Skip to content

Fold expressions

[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