Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fold expression with comma operator and variadic template parameter pack

#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?

like image 844
Ke Zhang Avatar asked Nov 16 '18 02:11

Ke Zhang


People also ask

What are C++ Variadic templates and fold expressions?

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.

How to reduce the parameter pack with a fold expression?

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.

What is a fold expression in C++?

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.

What is a variadic template pack in Python?

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.


2 Answers

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';
like image 140
Barry Avatar answered Oct 23 '22 22:10

Barry


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.

like image 35
chris Avatar answered Oct 23 '22 22:10

chris