#include<iostream>
using namespace std;
template<typename ...Args>
void output_argus(Args&&... args)
{
((cout << args << '\n'), ...); // #1
(... , (cout << args << '\n')); // #2
}
int main()
{
output_argus(1, "test", 5.6f);
}
Based on c++ operator doc, ','
is a left to right operator. It is meaning a, b, c, d
meaning (((a, b), c),d)
not (a, (b, (c, d)))
. This is important if a, b, c, d are statements.
However, based on fold expression doc, for ','
which should use unary left fold.
My question why both statements in my code are working? Shouldn't only #2 work?
And also how to understand ...
and args
. and nested fold expression?
What are C++ variadic templates and fold expressions? Introduced in C++11, a variadic template is a function or class template that accepts a parameter pack. The pack can have arbitarary number of parameters having different types.
With fold expressions, you can directly reduce the parameter pack with the help of the binary operator: (... && args). Here is the output of the program. A fold expression applies a binary operator to a parameter pack. A fold expression can apply the binary operator in two different ways. With and without an initial value.
A fold expression applies a binary operator to a parameter pack. A fold expression can apply the binary operator in two different ways. With and without an initial value. There is a subtle difference between the algorithm allVar and all. All have the default value true for the empty parameter pack.
The pack can have arbitarary number of parameters having different types. At the end of compile-time, a variadic template function is translated into multiple solid functions that each of them accepts a certain number of parameters with certain types. There is no explicit for-loop commands to iterate the pack parameters.
Let's say we're folding 3 expressions over a binary operator, with a unary fold. We have two options here: (xs @ ...)
(a unary right fold) and (... @ xs)
(a unary left fold).
(xs @ ...)
expands out to (a @ (b @ c))
(... @ xs)
expands out to ((a @ b) @ c)
What can we say about the difference between the expressions a @ (b @ c)
and (a @ b) @ c
? If @
is associative over these types, then those two expressions are identical. That's what associative means. If you had a parameter pack of integers, then a unary left fold over +
and a unary right fold over +
will have the same value (modulo overflow), because addition is associative. Subtraction, on the other hand, is not associative. (xs - ...)
and (... - xs)
mean very different things.
Likewise, the ,
operator in C++ is associative. It doesn't matter which way you parenthesize the expressions. ((a, b), c)
and (a, (b, c))
both evaluate and discard a
, then evaluate and discard b
, then evaluate c
and that's the result. It's easier to see if you reduce the expressions to just letters why this is the case.
As a result, both ((cout << args << '\n'), ...)
and (... , (cout << args << '\n'))
do the same thing, and they both effectively mean:
cout << args1 << '\n';
cout << args2 << '\n';
// ...
cout << argsN << '\n';
From the linked page, your #1 expands as follows:
((cout << args₀ << '\n'), ((cout << args₁ << '\n'), (cout << args₂ << '\n')));
Removing some repetition to make it cleaner:
args₀, (args₁, args₂)
For #2, the expansion boils down to:
(args₀, args₁), args₂
Let's walk through the evaluation of each one.
#1:
args₀ , (args₁, args₂)
^^^
The underlined comma evaluates the left side, printing 1
. Then the right side is evaluated, which evaluates the print of args₁
, printing test
, then the print of args₂
, printing 5.6
.
#2:
(args₀, args₁) , args₂
^^^
The underlined comma evaluates the left side. This triggers evaluation of the other comma, which evaluates the print of args₀
, printing 1
, then evaluates the print of args₁
, printing test
. Now the underlined comma is done evaluating the left side and evaluates the right side, printing 5.6
.
As you can see, both produce the same evaluation order of each individual argument despite the grouping of parentheses being different.
Note that in general, this might not always hold. Some operators, such as +
, do not have a guaranteed evaluation order like the comma operator does. Were such an operator used instead of the comma to join the print expressions, the compiler could ultimately choose to evaluate the individual argument prints in any order.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With