In one of my previous articles, “compile-time repeat & noexcept-correctness”, I looked at the design and implementation of a function repeat(f) that, when called, expands into n calls to f at compile time.
For example:
repeat<4>([]{ std::cout << “hello\n”; });
is equivalent to the following code:
[]{ std::cout << “hello\n”; }([]{ std::cout << “hello\n”; });
[]{ std::cout << “hello\n”; }();
[]{ std::cout << “hello\n”; }();
[]{ std::cout << “hello\n”; }();
This mechanism can be viewed as a limited form of iteration at the compilation stage. When writing generalized code, there is often a need for similar constructs for:
- enumerating the list of types Ts… at the compilation stage;
- enumerating the list of values Xs… at the compilation stage;
- iterating over the range [B, E) during compilation;
- enumerating the list of types Ts… while preserving the iteration index.
In this paper, we will look at the implementation of these constructs using the C++20 innovation P0428: “Familiar pattern syntax for generalized lambdas” (Louis Dionne).
This feature is available in g++ 8.x.
New template syntax for lambdas
An example of a C++20 generalized lambda that takes a template parameter T and works with std::vector:
auto print_vector = [](const std::vector& v) {
for (const auto& x : v) {
std::cout << x;
}
};
print_vector(std::vector{0, 1, 2, 3, 4, 5});
This code compiles into the following anonymous class:
struct {
template
void operator()(const std::vector& v) const {
for (const auto& x : v) {
std::cout << x;
}
}
};
Iteration over the list of types
Example usage:
For_types([]() {
std::cout << typeid(T).name();
});
Implementation:
template
constexpr void for_types(F&&& f) {
(f.template operator()(), …);
}
Iterate over a list of values
Example usage:
for_values<2, 8, 16>([]() {
std::array a;
something(a);
});
Implementation:
template
constexpr void for_values(F&&& f) {
(f.template operator()(), …);
}
Iterating over a range
Example usage:
for_range<-5, 5>([]() {
std::cout << X << ' ';
});
Output:
-5 -4 -3 -2 -1 0 1 2 3 4
Implementation:
template
constexpr void for_range(F&&& f) {
using t = std::common_type_t;
[&f]<auto.... Xs>(std::integer_sequence<t, Xs...>) {
for_values<(B + Xs)...>(f);
}(std::make_integer_sequence<t, E - B>{});
}
Enumerating a list of types with indices
Example usage:
enumerate_types([]() {
std::cout << I << “: ‘ << typeid(T).name() << ’\n';
});
Output:
0: i
1: f
2: c
Implementation:
template
constexpr void enumerate_types(F&&& f) {
[&f](std::index_sequence) {
(f.template operator()(), …);
}(std::index_sequence_for{});
}
Conclusion
The new template syntax in C++20 makes constructs such as for_types and for_range possible and convenient, making them much easier to use than in C++17. The ability to create and use std::integer_sequence directly inside a lambda simplifies the code and makes it more readable.