# Throw away pack expansion results in C++

This article describes how to throw away template parameter pack expansions in C++.

## Motivation

In template metaprogramming (TMP) in C++, you may want to discard pack expansion results after evaluating them. However, pack expansions are allowed in limited contexts. For example, the following code will not compile due to a syntax error:

/// Assign 0 to all of arr[Indices...]
template <int... Indices>
constexpr void Clear(Array& arr) noexcept {
arr[Indices] = 0...;
// ↑ error!
// You cannot expand parameter pack here.
}

Therefore, one needs a particular idiom to throw away expanded values in such a situation.

The following text describes how to discard evaluation results of pack expansions before C++17 and after C++17.

## After C++17

It is easier in C++17 than in C++14. One can throw away pack expansions by fold expressions using operator,(). Fold expressions are a new syntax introduced in C++17, which can recursively apply binary operators to parameter packs. Typically, arithmetic or logical operators like + and && are widely used, but operator,() is also applicable.

template <int... Indices>
constexpr void Clear(Array& arr) noexcept {
((arr[Indices] = 0), ...);
}

Compilers interpret the above example as (arr[I0] = 0), ((arr[I1] = 0), ((arr[I2] = 0), ((.... The expression executes all expressions from the starting one and does a pack expansion while throwing away results.

As described above, it is straightforward to discard pack expansion results in C++17.

## Before C++17

Before C++17, one can throw away pack expansion results only in the following situations:

• Function arguments f(args...)
• Initializer lists {args...}

I recommend the latter because in the former, the order of evaluation passed to a function is unspecified. Example:

/// （not recommended） A function that ignores all arguments
template <typename... Args>
constexpr void ConsumeValues(Args&&...) noexcept {}

template <int... Indices>
constexpr void Clear(Array& arr) noexcept {
ConsumeValues((arr[Indices] = 0)...);
// ↑ It is ok, but I don't recommend this code
// Because the order of argument evaluation is unspecified.
}

Therefore, the following code is better to throw away values before C++17. This idiom is sometimes called swallow idiom.

/// An empty struct that is construcrible by any value
struct Anything {
template <typename T>
constexpr Anything(T&&) noexcept {}
};

/// A empty function that takes an initializer list of Anything
/// As Anything is constructible by any type, you can discard values by
/// 
/// ConsumeValues({/* 式1 */, /* 式2 */, ...});
/// 
constexpr void ConsumeValues(std::initializer_list<Anything>) noexcept {}

First, define a struct Anything that is constructible by any value. The type is implicitly convertible from any instance, so std::initializer_list<Anything> acts as a trash box for any expression.

ConsumeValues({33, 4, "hoge", 44.5});
/// All values is converted Anything, and removed away

Using this trash box, you can discard the expansion results of parameter packs.

template <int... Indices>
constexpr void Clear(Array& arr) noexcept {
ConsumeValues({(arr[Indices] = 0)...});
}

Because arguments of initializer lists are evaluated from beginning to end, the swallow idiom is guaranteed to evaluate expansions from beginning to end.

template <int... Indices>
constexpr void Func(Array& arr) noexcept {
ConsumeValues({(Something(arr[Indices]),0)...});
}