Hm, the answer of user Benjamin Buch seems to be
- a little bit over-complicated for this use case
- and does not address the question of the OP fully, because he wanted to apply a function for his N arguments
Regarding 1.: For Just adding few values, we do not need std::reduce. So, putting the value first in a container, here in a std::initializer_list, and then reducing this container, is for adding values by far too complicated. std::reduce has big advantages, if you want to parallelize or vectorize big data using associative and commutative operators. The idiomatic preferred solution would be to use fold expressions. I will explain this below.
Regarding 2.: This is not addressed at all. I will show a solution, also based on fold expressions, below.
Building the sum with fold expressions. If you look in the CPP reference here, you can see, how parameter packs (in your example Args... args) can be easily reduced. If you look there, then you can read:
Syntax:
( pack op ... ) (1)
( ... op pack ) (2)
( pack op ... op init ) (3)
( init op ... op pack ) (4)
1) unary right fold
2) unary left fold
3) binary right fold
4) binary left fold
If you replace, for your uses case "pack" with "args" and "op" with "+", then it would read like that:
( args + ... )
( ... + args )
( args + ... + sum )
( sum + ... + args )
There are use case for all variants. But let me first show you the easiest solution based on an unary right fold:
#include <iostream>
template <typename...Args> // Unary right fold
auto sum1(Args ... args) {
return (args + ...);
}
int main() {
std::cout << sum1(1, 2, 3, 4) << '\n';
}
And this is really very compact simple.
You can read in the CPP Reference, what will happen here.
Explanation
The instantiation of a fold expression expands the expression e as follows:
- Unary right fold (E op ...) becomes (E1 op (... op (EN-1 op EN)))
For our above fold expression (args + ...) we would get the following:
(1 + ( 2 + (3 + 4))). For the '+' operator, we can omit all braces and finally get: 1 + 2 + 3 + 4.
Nice
Please see below a piece of code where we use all 4 variants of fold expressions:
#include <iostream>
#include <tuple>
template <typename...Args> // Unary right fold
auto sum1(Args ... args) {
return (args + ...);
}
template <typename...Args> // Unary left fold
auto sum2(Args ... args) {
return (... + args);
}
template <typename...Args> // Binary right fold
auto sum3(Args ... args) {
std::tuple_element_t<0, std::tuple<Args...>> sum{};
sum = (args + ... + sum);
return sum;
}
template <typename...Args> // Binary left fold
auto sum4(Args ... args) {
std::tuple_element_t<0, std::tuple<Args...>> sum{};
sum = (sum + ... + args);
return sum;
}
int main() {
std::cout << sum1(1, 2, 3, 4) << '\n';
std::cout << sum2(1, 2, 3, 4) << '\n';
std::cout << sum3(1, 2, 3, 4) << '\n';
std::cout << sum4(1, 2, 3, 4) << '\n';
}
.
Next. To the second part. How to apply a function on each argument of a parameter pack. There are also many potential solutions, but, because I was explaining about fold expressions, I will show a solution for small functions, so, specifically lambdas, by using fold expressions.
And the main part of the solution here is to use the 'comma` operator in conjunction with unary right fold.
Since the comma-operator is not often used, lets read again about it here.
so, lhs, rhs does the following:
First, the left operand, lhs, is evaluated and its result value is discarded.
Then, a sequence point takes place, so that all side effects of lhs are complete.
Then, the right operand, rhs, is evaluated and its result is returned by the comma operator as a non-lvalue.
And this can be fantastically combined with an unary right fold.
Lets take a lambda in its simplest form: []{}. You see just the capture and the body. This is a fully valid Lambda. If you want to call this lambda, then you can simply write []{} (). This calls an empty lambda and does nothing. Just for demonstration purposes: If we use for the sum example:
template <typename...Args>
auto sum5(Args ... args) {
std::tuple_element_t<0, std::tuple<Args...>> sum{};
// comma lhs: Lambda comma rhs
([&]{sum += args;} () , ...);
return sum;
}
then the follwoing will happen:
- We define a lambda and capture all outer variables via reference, here especially
sum
- In the body we add args to the sum
- Then we call the function with
(). This is the "lhs" of the comma operator
- Then we do a unary right fold and call the lambda again with the next args
- and so on and so on.
If you analyze this step by step, you will understand it.
The next step is then easy. Of yourse can stuff more functionality in your lambda. And with that, you can apply a function on all arguments of a parameter pack.
This will be an example for your intended functionality:
#include <iostream>
#include <tuple>
#include <utility>
#include <limits>
#include <iomanip>
template <typename...Args>
using MyType = std::tuple_element_t<0, std::tuple<Args...>>;
template <typename...Args>
std::pair<bool, MyType<Args...>> sumsWithOverflowCheck(Args ... args) {
bool overflow{};
MyType <Args...>sum{};
([&] // Lambda Capture
{ // Lambda Body
const MyType<Args...> maxDelta{ std::numeric_limits<MyType<Args...>>::max() - sum };
if (args > maxDelta) overflow = true;
sum += args;
} // End of lambda body
() // Call the Lampda
, ...); // unary right fold over comma operator
return { overflow, sum };
}
int main() {
{ // Test 1
const auto& [overflow, sum] = sumsWithOverflowCheck(1, 2, 3, 4);
std::cout << "\nSum: " << std::setw(12) << sum << "\tOverflow: " << std::boolalpha << overflow << '\n';
}
{ //Test 2
const auto& [overflow, sum] = sumsWithOverflowCheck(1,2,(std::numeric_limits<int>::max()-10), 20, 30);
std::cout << "Sum: " << std::setw(12) << sum << "\tOverflow: " << overflow << '\n';
}
}
Of course you would elimitate all unnecessary line breaks in the lambda, to make it compacter.
add(add(arg0, arg1), add(arg2, arg3))should be the same asadd(add(add(add(arg0, arg1), arg2), arg3)). You just need to get that type of syntax out of a fold expression.