In one of my previous articles, “compile-time
noexcept-correctness”, I have covered the design and implementation of a simple
repeat<n>(f) function that, when invoked, expands to
n calls to
f during compilation. E.g.
…is roughly equivalent to…
If you squint, this is a very limited form of compile-time iteration. When writing generic code, I’ve often needed similar constructs in order to express the following actions:
iterate over a compile-time list of types
iterate over a compile-time list of values
iterate over a compile-time integral range
enumerate a compile-time list of types
Ts...(i.e. iteration alongside an index).
In this article I’m going to show you how to implement the above constructs, relying on a new nifty addition to C++20 lambdas: P0428: “Familiar template syntax for generic lambdas”, by Louis Dionne.
This feature is currently available in
familiar template syntax
Here’s an example of a C++20 generic lambda, taking a single template parameter
T and accepting an
This roughly desugars to the following anonymous closure type:
Compared to a C++14 generic lambda, this feature allows users to easily:
constrain generic lambdas to any instantiation of a particular class;
“match” a template parameter (or parameter pack) without having to introduce an additional function.
The second point is particularly useful when dealing with type lists and utilities such as
Another interesting thing that can be done on both C++14 and C++17 generic lambdas is directly calling
operator() by explicitly passing a template parameter:
The C++14 example above is quite useless: there’s no way of referring to the type provided to
operator() in the body of the lambda without giving the argument a name and using
decltype. Additionally, we’re forced to pass an argument even though we might not need it.
The C++20 example shows how
T is easily accessible in the body of the lambda and that a nullary lambda can now be arbitrarily templatized. This is going to be very useful for the implementation of the aforementioned compile-time constructs.
iteration over a type list
The first construct we’re going to implement is a simple “loop” over a list of user-provided types. Here’s a usage example:
The code above prints
"ifc". I like to read it as: “for the types
char, please execute the following action”. (The “please” is not mandatory.)
The implementation of
for_types is as follows:
Ts...parameter pack cannot be deduced, and is explicitly provided by the user;
the closure is taken as a forwarding reference, in order to accept non-
fis not perfectly-forwarded inside the body of the function as it could be invoked multiple times;
for_typesis marked as
constexpreven though it returns
void- this allows it to be used inside
for_types is useful in various scenarios - as an example, imagine unit testing a component or a function over a set of fixed types, or checking if an
std::any instance contains one of a set of given types.
iteration over a compile-time list of values
Let’s begin with a usage example:
This is another useful construct that allows “compile-time iteration” over a set of values, which can be used as part of constant expressions. The implementation is almost identical to
for_types, but we’re using
auto... instead of
auto as a non-type template parameter was introduced in C++17 thanks to P0127: “Declaring non-type template parameters with
auto”, by James Touton and Mike Spertus.
iteration over a range
As always, let’s start with a usage example:
-5 -4 -3 -2 -1 0 1 2 3 4
The implementation is quite interesting, and depends on
[B, E)range via
autonon-type template parameters. The common type between those is computed and aliased as
E - Bis created with:
the sequence is used to invoke a C++20 generic lambda which takes
auto... Xsas a non-type template parameter pack. The values of
Xs...are deduced by “matching” them from the
finally, the body of the lambda invokes
for_values<(B + Xs)...>(f), which expands to an invocation of
ffor every value in the
The implementation of
for_range is a compelling example of how C++20 generic lambdas can make it really easy to create and use a
std::integer_sequence<T, Xs...> on the spot, without having to invoke a separate implementation function just to “match”
enumeration of a list of types
This construct is useful when you want to iterate over a list of types at compile-time, while also keeping track of the current iteration index as a constant expression. I used this in my experimental library
orizzonte to implement
when_any abstractions for the composition of asynchronous future graphs.
This prints out:
The idea is as follows: we’ll accept a template parameter pack
Ts... containing the types from the user, and then generate an index pack of equal length using
std::index_sequence_for. Finally, both packs will be expanded at the same time with a fold expression.
for_range, a C++20 generic lambda is being used to create and consume a
std::index_sequence on the spot.
The new “familiar template syntax” for lambdas introduced in C++20 makes constructs such as
for_range viable and way more readable compared to C++17 alternatives.
Being able to expand a sequence on the spot without having to create an extra
detail function is also a great advantage brought from this new feature.