Until C++17, expanding parameter packs required a lot of boilerplate such as recursive template functions. Recently, binary and unary folding expressions were introduced in C++17. Folding expressions reduces the code that is needed to un-fold the parameter pack around binary and unary operators.
pre-C++17 parameter packs
Lets assume we want to create a function that takes arbitrary number of arguments and concatenate them into a std::string. This function has to be enabled for streamable types for compiler errors instead of run-time errors, however, this part is out of the scope of this article. Just keep this in mind :D
// Base case of the concatenation function
template < class TType >
std::string concatenate(TType _Ty) {
std::stringstream ss;
ss << _Ty;
return ss.str();
}
// Construct a string by concatenation aribtrary
// number of arguments
template < class TFirst, class... Args >
std::string concatenate(TFirst _Ty, Args... args) {
std::stringstream ss;
ss << args << concatenate(args...);
return ss.str();
}
In this example, we had to expand the parameter pack using recursive functions. This means constructing number of std::stringstreams objects equal to the number of parameters, this is inefficient.
C++17 folding expressions
In C++17, parameter packs can be un-folded around 32 binary operators which are (+
-
*
/
%
^
&
|
=
<
>
<<
>>
+=
-=
*=
/=
%=
^=
&=
|=
<<=
>>=
==
!=
<=
>=
&&
||
,
.*
->*
). Fold operation can be done in four different ways.
For a pack E with N elements, operator op, and optional initial arguments I:
Syntax | Interpretation | Explanation |
---|---|---|
(E op ...) |
unary right fold | (E1 op (E2 (… op (EN-1 op EN)))) |
(... op E) |
unary left fold | ((((E1 op E2) op …) op EN-1 ) op EN) |
(E op ... op I) |
binary right fold | (E1 op ( E2 op (… op ( EN-1 op (EN op I))))) |
(I op ... op E) |
binary left fold | (((((I op E1) op E2) op …) op EN-1) op EN) |
Now, we can re-write the concatenate function in a more efficient way by creating one std::stringstream object and unfold the parameter pack without the need of recursion.
template < class... Args >
std::string concatenate(Args... args) {
std::stringstream ss;
// ss << E_1 << E_2 << .. << E_N-1 << E_N
(ss << ... << args); // C++17 fold expression
return ss.str();
}
Parameter packs of length zero
As always, we do not want to fall into the infamous undefined behaviour. Applying unary operators to a parameter pack of length zero could lead to undefined behaviour except three cases
logical operator AND
&&
, the expression evaluates to truelogical operator OR
||
, the expression evaluates to falsecomma operator
,
, the expression evaluates to void()
- To read more, please check the following references:
- fold expressions
- cppreference