Aug 19

C++17 and Fold Expressions

So I’ve just come across this very cool feature in C++17 that I plan on using called ‘fold expressions’. This is basically a way of expanding a bunch of operations. It is also very hard for me to wrap my head around, so we’re going to go through a (very simple) example here, since people don’t seem to explain what exactly happens.

This simple example prints out everything that is passed into the method to stdout:

template <typename... argn>
void fold_test_types( argn... params ){
    (std::cout << ... << params);
}

int main(){
    fold_test_types( 3, 4, "hi", 8);
}

The output is 34hi8, because we forgot to add any newlines or spaces, but that’s fine for this example.

Now what really tripped me up here is the fact that in the fold expression, we’re using the << operator to distinguish things, and it makes it very confusing as to what is actually happening. With fold expressions, the data between the parentheses is expanded, kinda like a template. Let’s look at the rules real quick:

( 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


op – any of the following 32 binary operators: + – * / % ^ & | = < > << >> += -= *= /= %= ^= &= |= <<= >>= == != <= >= && || , .* ->*. In a binary fold, both ops must be the same.
pack – an expression that contains an unexpanded parameter pack and does not contain an operator with precedence lower than cast at the top level (formally, a cast-expression)
init – an expression that does not contain an unexpanded parameter pack and does not contain an operator with precedence lower than cast at the top level (formally, a cast-expression)
Note that the open and closing parentheses are part of the fold expression.

https://en.cppreference.com/w/cpp/language/fold

This is a bit hard to understand, but the … is basically a keyword when it is in the parentheses like this. ‘pack’ is the name of the parameters, e.g. in our example it is ‘params’.

Looking at these rules, the rule that we are following in this example is #4, the binary left fold. Let’s look at the fold expression again, with some comments:

(std::cout << ... << params);
|    |      |  |   |    |  ^----ending paren
|    |      |  |   |    ^---- pack(params) - see definition above
|    |      |  |   ^---- operator(<<)
|    |      |  ^---- ...(keyword?)
|    |      ^---- operator(<<)
|    ^--- init(std::cout) - see definition above
^----Starting paren   

With these broken out, it should now be clear why this is #4, the binary left hold. The rules on how it folds are as follows:

1) Unary right fold (E op …) becomes (E1 op (… op (EN-1 op EN)))
2) Unary left fold (… op E) becomes (((E1 op E2) op …) op EN)
3) Binary right fold (E op … op I) becomes (E1 op (… op (EN−1 op (EN op I))))
4) Binary left fold (I op … op E) becomes ((((I op E1) op E2) op …) op EN)

https://en.cppreference.com/w/cpp/language/fold

Since we are using #4, apply the rule for #4. When the compiler runs, the code effectively becomes the following with the template substitution and the fold expression expansion:

void fold_test_types( int arg1, int arg2, std::string arg3, int arg4 ){
    ((((std::cout << arg1) << arg2) << arg3) << arg4);
}

int main(){
    fold_test_types( 3, 4, "hi", 8);
}