Fold expressions is the extension for already introduced variadic templates in C++11.
It allows to pack and unpack template parameters in unary and binary operations.
For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
template<typename ... Args> auto Sum(Args... args) { return (args + ...);} template<typename ... Args> bool all(Args ... args) { return (args && ...);} template<typename ... Args> void printer(Args ... args){ (std::cout << ... << args) << std::endl; } int main() { std::cout << all(true, true, true) << std::endl; printer(1, 2, 3); std::cout << Sum(1, 2, 3, 4, 5) << std::endl; return 0; } |
Example with pushing values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
template<typename T, typename ... Args> void push_all(std::vector<T>& vector, Args ... args) { (vector.push_back(args), ...); } int main() { std::vector<int> vector_ = { 1, 2, 3, 4, 5 }; push_all(vector_, 1, 1, 1, 2,22, 2,2); for (auto value : vector_) std::cout << value << " "; return 0; } |
Before C++17 template adding sum was:
1 2 3 4 5 6 7 8 9 10 11 12 |
auto Sum_old() {return 0;} template<typename T1, typename ... Ts> auto Sum_old(T1 t1, Ts ... ts) { return t1 + Sum_old(ts ...); } int main() { std::cout << Sum_old(0,3) << std::endl; return 0; } |
Syntax for fold expressions:
( pack op … ) //(1)
( … op pack ) //(2)
( pack op … op init ) //(3)
( init op … op pack ) //(4)
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) |
translation:
(… op pack) ==> ((pack1 op pack2 op …) op packN
(init op … op pack) ==> (((init op pack1) op pack2) op …) op packN
(pack op …) ==> pack1 op (… op (packN-1 op packN))
For example:
{1, 2, 3, 4} => args + …; => 1 + (2 + (3 + 4))
Also, there are default values for empty parameter pack:
- && — true
- || — false
- , — void()
Fold expressions in assembly
The code in C++17 looks simpler than in C++14
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// C++17 template<typename ... Ts> auto Sum(Ts ... ts) { return (ts + ...); } // C++14 auto Sum() {return 0;} template<typename T1, typename ... Ts> auto Sum(T1 t1, Ts ... ts) { return t1 + Sum(ts...); } int main() { std::cout << Sum(1, 2, 3, 4, 5); return 0; } |
In C++14 we stack usage because of recursion like this each time:
1 2 3 4 5 6 7 8 9 10 11 |
mov esi,DWORD PTR [rbp-0x4] mov edi,DWORD PTR [rbp-0x8] mov DWORD PTR [rbp-0xc],esi call 400810 <auto Sum<int>(int)> mov esi,DWORD PTR [rbp-0xc] add esi,eax mov eax,esi add rsp,0x10 pop rbp ret nop DWORD PTR [rax+0x0] |
but with optimization -O3 we will have avoided stack usage and only end value will be moved to std::cout.
1 2 3 4 |
push rax mov edi,0x601060 mov esi,0xf ⇐====== our result 15 call 400560 <std::ostream::operator<<(int)@plt> |
in case of C++17:
1 2 3 4 5 6 7 8 9 10 11 |
mov ecx,DWORD PTR [rbp-0x4] mov edx,DWORD PTR [rbp-0x8] mov esi,DWORD PTR [rbp-0xc] mov edi,DWORD PTR [rbp-0x10] add edi,DWORD PTR [rbp-0x14] add esi,edi add edx,esi add ecx,edx mov eax,ecx pop rbp ret |
There is no any recursion even without optimization level. In case of optimization -O3.
We will have the same assembly as for C++14:
1 2 3 4 |
push rax mov edi,0x601060 mov esi,0xf ⇐====== our result 15 call 400560 <std::ostream::operator<<(int)@plt> |
Lambda inside fold expression
Example:
1 2 3 4 5 6 7 8 9 10 11 12 |
template<typename ... Args> auto printer(Args ... args) { return ([args](){ std::cout << args << std::endl; return 0; }() + ...); } int main() { printer(1,2,3); return 0; } |
The standard paper related to Fold expressions: p0036r0