Fold Expressions in C++ 17

Share the Article

Fold expressions is a new feature in C++ 17. This feature is related to variadic arguments of templates. Fold expressions enables the C++17 compiler to apply same binary operation to all the arguments of parameter pack. The compiler first applies the binary operation to first set of 2 arguments. After this it applies the result to third argument and so on.

For instance,

If (T . . . args) is the variadic argument of a template function, then the fold expression may look like as follows:

(... <binary_operation> args); //fold expression

The compiler shall unfold this expression and apply binary operation as follows:

( (args1 <binary_operation> args2) <binary_operation> args3) <binary_operation> args4 . . and so on

Please note, that it is mandatory to place the fold expression in brackets ” ( ) “

Basic Example of using fold expressions in C++ 17

In the following example, the function sum( ) takes variadic list of arguments. This function shall compute total sum of all the arguments using fold expressions.

In this example, the fold expression is => ( . . . + vals). The compiler shall unfold it like, (vals1 + vals2) + vals3 and so on.

#include <iostream> //main header using namespace std; //for namespace template<typename ... T> void sum(T ... vals) { int total = (... + vals); //Fold expression cout << "Total = " << total << endl; } int main() { sum(1); // = (1) sum(1, 2); // = (1 + 2) sum(1, 2, 3); // = ((1 + 2) + 3) sum(1, 2, 3, 4); // = (((1 + 2) + 3) + 4) return 0; }

Output

Output of code using fold expressions in C++ 17

Generic version of same example

The above example is too raw and it does not make use of template type. The following example is the better version and we can use this to work with different types.

#include <iostream> //main header using namespace std; //for namespace template<typename ... T> void sum(T ... vals) { cout << "Total = " << (... + vals) << endl;; } int main() { sum(1); sum(1, 2); sum(1, 2, 3); sum(1, 2, 3, 4); cout << endl; sum(1.1); sum(1.1, 2.2); sum(1.1, 2.2, 3.3); sum(1.1, 2.2, 3.3, 4.4); cout << endl; sum(std::string("A")); sum(std::string("A"), std::string("B")); sum(std::string("A"), std::string("B"), std::string("C")); sum(std::string("A"), std::string("B"), std::string("C"), std::string("D")); return 0; }

Output:

Output of code adding and concatenating items and strings using fold expressions.

Pre and Post syntax of operations in Fold expressions in C++ 17

The above example uses a syntax where ellipses occur on left-side of binary operation. This is “pre” syntax. However, it is possible to place the ellipses to right-hand side and this is “post” syntax. The compiler unfolds “post” syntax in reverse direction.

Pre-add syntax: (... + vals) => (((vals1 + vals2) + vals3) + vals4) Post-add syntax: (vals + ...) => (vals1 + (vals2 + (vals3 + vals4))

Although for doing operations, like, addition, both the syntax shall generate same result. However, in other calculations, like, subtraction, division, etc. the result shall vary in using “pre” and “post” syntax. This is because, the position of arguments matter in these operations.

The following example demonstrates subtraction in both syntax.

#include <iostream> //main header using namespace std; //for namespace template<typename ... T> void diff_L2R(T ... vals) //Pre syntax { int diff = (... - vals); cout << "Difference = " << diff << endl; } template<typename ... T> void diff_R2L(T ... vals) //Post syntax { int diff = (vals - ...); cout << "Difference = " << diff << endl; } int main() { diff_L2R(10); // = (10) = 10 diff_L2R(10, 1); // = (10 - 1) = 9 diff_L2R(10, 1, 2);// = ((10 - 1) - 2) = 7 cout << endl; diff_R2L(10); // = (10) = 10 diff_R2L(10, 1); // = (10 - 1) = 9 diff_R2L(10, 1, 2);// = (10 - (1 - 2) = 11 return 0; }

Empty parameter list with fold expressions in C++17

In previous examples, that compiler can unfold the expressions according the to variadic arguments. However, in case, there are zero arguments, then compiler shall generate error. The variadic arguments should have atleast one argument to unfold the expression.

The following example demonstrates this.

#include <iostream> //main header using namespace std; //for namespace template<typename ... T> void sum(T ... vals) { cout << "Total = " << (... + vals) << endl;; } int main() { sum(); //No arguments passed return 0; }

Output:

Compiler error when not passing any argument in parameter pack of variadic argument list.

Exception in case of empty argument list

We know that in above example, the compiler throws error while unfolding the expression over operator+. However, there are exceptions:

  • operator&& (logical AND)
  • operator|| (logical OR)

For a fold expression, it is completely valid to accept empty arguments in above 2 cases. The logical AND shall generate always a TRUE value, whereas, logical OR shall always generate a FALSE value.

The following example, demonstrates this behavior with both “pre” and “post” type operations.

#include <iostream> //main header using namespace std; //for namespace template<typename ... T> void logical_ops(T ... vals) { cout << "AND_pre = " << ( ... && vals) << endl;; cout << "AND_post = " << (vals && ... ) << endl;; cout << "OR_pre = " << ( ... || vals) << endl;; cout << "OR_post = " << (vals || ... ) << endl;; } int main() { logical_ops(); //No arguments return 0; }

Output

Output of code using fold expressions with empty argument list.

Left and Right fold expressions

Apart from the exception case as explained with Logical AND & Logical OR, all other cases cause error with zero arguments.

Therefore, to ensure that compiler do not throw an error with zero arguments, we may use left or right fold. In these fold expressions, any specific value can become part of expression. This value can occur, either, in left or right of side of main fold expression. Generally, we may use number 0 to make such expressions.

The following examples uses value 0, to for binary fold expressions.

#include <iostream> //main header using namespace std; //for namespace template<typename ... T> void sum(T ... vals) { //Left-fold expression cout << "Total1 = " << (0 + ... + vals) << endl; //Right-fold expression cout << "Total2 = " << (vals + ... + 0) << endl; } int main() { sum(); return 0; }

Output

Output of code using left and right fold expressions in C++ 17

Calling some specific function for all arguments in fold expression in C++ 17

The compiler can unfold the expression with function call for each and every argument. Therefore, an expression like, following unfolds like below.

Given fold-expression, with function call - add_two( ) (... + add_two(vals) ); This shall evaluate to: (((add_two(vals1) + add_two(vals2)) + add_two(vals3) );

The following example applies this concept.

#include <iostream> //main header using namespace std; //for namespace int add_two(int x) { return (x+2); } template<typename ... T> void print(T ... vals) { int final_sum = (... + add_two(vals) ); cout << final_sum << endl; } int main() { print(1, 2); // = (add_two(1) + add_two(2)) = 7 return 0; }

How to check if all the variadic arguments are equal

The following example uses a different signature for template function getting variadic arguments. It receives the first argument in specific parameter and rest of arguments in variadic arguments. Now, with the concepts learnt so far, the compiler can call the function “is_equal” with first argument and each individual argument.

The given expression: (... && is_equal(val, vals) ) Unfolds to: ( (is_equal(val, vals1) && is_equal(val, vals2)) && is_equal(val, vals3) )

Therefore, the final result shall be Logical AND of all the “is_equal” function calls.

#include <iostream> //main header using namespace std; //for namespace bool is_equal(int x, int y) { return (x == y); } template<typename T1, typename ... T> void checkIfEqual(T1 val, T ... vals) { bool final_bool = (... && is_equal(val, vals) ); final_bool? cout << "EQUAL" : cout << "NOT-EQUAL"; cout << endl; } int main() { checkIfEqual(1, 2); checkIfEqual(2, 2); checkIfEqual(2, 2, 4, 8, 9); checkIfEqual(1, 1, 1, 1, 1, 1, 1); return 0; }

Output

Output of code which is iterating through variadic arguments using fold expressions.

How to print all the variadic arguments using fold expression

The following example uses “left-fold” to print all arguments. The “cout” shall be the left-fold item and “operator<<” is binary operation.

#include <iostream> //main header using namespace std; //for namespace template<typename ... T> void print(T ... vals) { (cout << ... << vals ) << endl; } int main() { print(1, 2, 3, 4, 5, 6); return 0; }

The above fold expression shall unfold as follows:

Fold expression: (cout << ... << vals ) Unfolded: (((cout << vals1) << vals2) << vals3) + vals4

Therefore, the output is

Output of code printing all the elements in variadic argument using fold expresssion in C++ 17

How to use TAB spaces between arguments

In the above example, there is no space or newline between the items being printed. For achieving this, the code need some additional handling.

Wrong method

We cannot use a fold expression as in following example. This is because, the fold expression is “(vals << . . . << ‘\t’)”.

Since, there is no “cout” inside this. Therefore, the compiler shall assume that ” << ” is left-shift and not stream insertion. Secondly, without a “cout” , the compiler shall use ASCII value of ‘\t’, which is 9 and this shall not treat this as TAB character.

Therefore, unfold it as follows:

#include <iostream> //main header using namespace std; //for namespace template<typename ... T> void print(T ... vals) { cout << ( vals << ... << '\t') ; } int main() { print(1); // Leads to => cout << (1 << 9); cout << endl; return 0; }

The program shall print value 512 in this case.

Correct Method-1 : Using Function Call with fold expressions in C++ 17

The first method calls function, putspace( ) for each variadic argument. This function firstly, prints a TAB character and then, returns back the same argument in fold expression.

#include <iostream> //main header using namespace std;//for namespace template<typename T> const T& putspace(T& i) { cout << '\t'; return i; } template<typename ... T> void print(T ... vals) { (cout << ... << putspace(vals) ) << endl ; } int main() { print(1, 2, 3, 4, 5, 6); return 0; }

Output

Output of code to print all variadic arguments with TAB character as separator.

Correct Method-2 : Using lambda expression

Another method is to use a lambda expression instead of a separate function. The following examples shows that lambda has exactly same code as it was in method-1.

#include <iostream> //main header using namespace std; //for namespace template<typename ... T> void print(T ... vals) { auto putnewline = [](const auto& i) -> const auto& { cout << endl; return i; }; (cout << ... << putnewline(vals) ) << endl ; } int main() { print(1, 2, 3, 4, 5, 6); return 0; }

Calling member functions with fold expressions

When there are multiple objects in the variadic argument list, the fold expression can call same method in each object. In this case, the comma-operator can do the trick. Please note, not only the member function, the comma-operator can call any regular function with this method.

The comma shall unfold expression as shown below:

Fold-expression: (..., func(args) ) Unfolds to: ((func(args1), func(args2) ), func(args3) . . and so on

The following example shall call member function “funda( )” with each object.

#include <iostream> //main header using namespace std; //for namespace class MFBase { public: virtual void funda() = 0; }; class MFDerived1 : public MFBase { public: void funda() { cout << "MF Derived #1" << endl; } }; class MFDerived2 : public MFBase { public: void funda() { cout << "MF Derived #2" << endl; } }; template<typename ... T> void print(T ... vals) { (... , vals->funda() ); } int main() { MFBase *b1 = new MFDerived1(); MFBase *b2 = new MFDerived2(); print(b1, b2, b1, b2, b1); return 0; }

Output

Output of code which is calling member functions on multiple objects using variadic argument list.

Main Funda: The fold expression is powerful technique to apply same operation to all arguments in parameter pack.

Related Topics:

Class Template Argument Deduction in C++17
What is a Tuple, a Pair and a Tie in C++
C++ Multithreading: Understanding Threads
What is Copy Elision, RVO & NRVO?
Lambda in C++11
Lambda in C++17
std::chrono in C++ 11
Thread Synchronization with Mutex
Template type deduction in functions
How std::forward( ) works?
How std::move() function works?
What is reference collapsing?

Share the Article

Leave a Reply

Your email address will not be published. Required fields are marked *