[Four types of fold expressions]
[Example: adding values]
[Example: simple printing any number of arguments of arbitrary type]
[Example: extended printing any number of arguments of arbitrary type]
[Example: calling a function for each arg (comma operator)]
[Example: checking types]
[Example: printing a variadic tuple]
Fold expressions can be used to automatically expand a variadic parameter pack. Instead of writing recursive templates for N=1 and N-1 you can use a single definition using a parameter pack and an optional initial value.
Four types of fold expressions
- unary right fold:
(args op ...)
- unary left fold:
(... op args)
- binary right fold:
(args op ... op init)
- binary left fold:
(init op ... op args)
For the full list of possible operators see https://en.cppreference.com/w/cpp/language/fold
Recommendations:
Always use left fold syntax, which starts with combining the first two arguments. The right fold syntax “(args + …)” first evaluates the last two arguments. Depending on the used arguments one of the syntax options may lead to a compiler error (e.g. combining std::string(“Hello”) + “world” + “!” only succeeds when starting with std::string argument).
Example: adding values
template<typename... T>
auto Add(T... args)
{
return (... + args); // unary left fold syntax
// expands to (((arg1 +arg2)+arg3) + ...
}
auto sum = Add(40,-4, 3, 3); // sum=42
You can use an optional initial value. Thus the call will also work with an empty parameter pack: “return (0 + … + args)”
Example: simple printing any number of arguments of arbitrary type
template<typename T>
const T& AddSpace(const T& arg)
{
std::cout << ' ';
return arg;
}
template<typename Arg1, typename... OtherArgs>
void WriteToStdOut(const Arg1& arg1, const OtherArgs&... otherArgs)
{
std::cout << arg1;
(std::cout << ... << AddSpace(otherArgs)) << '\n'; // binary left fold syntax
}
WriteToStdOut("Hello", "world", 42);
Example: extended printing any number of arguments of arbitrary type
Helper class to add a blank after each param and to optionally write type info:
struct MyOutStream
{
bool showTypeInfo{ false };
template<typename T>
MyOutStream& operator<<(const T& p)
{
if (showTypeInfo)
{
std::cout << std::format("{} ({}) ", p, typeid(p).name());
}
else
{
std::cout << p << ' ';
}
return *this;
}
};
The print function with the fold expression:
template<typename... Args>
void Print(bool withTypeInfo, Args... args)
{
MyOutStream mos{ .showTypeInfo = withTypeInfo };
(mos << ... << args);
std::cout << '\n';
}
Client code:
const bool WITH_TYPE_INFO = true;
const bool WITHOUT_TYPE_INFO = false;
long num{ 17236 };
std::string s{"anything" };
Print(WITHOUT_TYPE_INFO, 17, 3.1, num);
Print(WITHOUT_TYPE_INFO, "Something", true, 42, s);
Print(WITH_TYPE_INFO, 17, 3.1, num);
Print(WITH_TYPE_INFO, "Something", true, 42, s);
Output:
17 3.1 17236
Something 1 42 anything
17 (int) 3.1 (double) 17236 (long)
Something (char const * __ptr64) true (bool) 42 (int) anything (class std::basic_string<char,...
The expression mos << ... << args
automatically expands the passed arguments (e.g. 17, 3.1, num
) to mos << 17 << 3.1 << num
. This is a binary left fold expression.
Example: calling a function for each arg (comma operator)
template<typename... Types>
void CallSomeFunction(const Types&... args)
{
(... , SomeFunction(args));
// calls SomeFunction(arg1), SomeFunction(arg2), ...
}
Example: checking types
#include <type_traits>
template<typename T1, typename... TN>
struct AllTypesAreTheSame
{
static constexpr bool value = (std::is_same<T1,TN>::value && ...);
}
template<typename T1, typename... TN>
constexpr bool PassedArgsHaveSameType(T1, TN...)
{
return (std::is_same<T1,TN>::value && ...);
}
AllTypesAreTheSame<int, Size, decltype(someVar)>::value;
PassedArgsHaveSameType(23, someVar, otherVar);
Example: printing a variadic tuple
The following template supports printing of a tuple with any number and types of elements:
template<typename... T>
constexpr void PrintTuple(const std::tuple<T...>& tup)
{
auto lpt = [&tup] <size_t... I> (std::index_sequence<I...>) constexpr
{
// unary left fold expression with comma operator
(..., (std::cout << std::format((I ? ", {}" : "{}"), std::get<I>(tup))));
std::cout << '\n';
};
lpt(std::make_index_sequence<sizeof...(T)>());
}
sizeof...
returns the number of elements in a parameter pack.std::make_index_sequence
<N>() generates 0,1,2,..,N-1 when N is the number of tuple elements
Client code:
std::tuple someFruits{"Apple", "Banana", "Grapes"};
std::tuple someNumbers{"One", 4711, 3.14, "Twelve"};
PrintTuple(someFruits);
PrintTuple(someNumbers);
Output:
Apple, Banana, Grapes
One, 4711, 3.14, Twelve