Suppose I want to call some templates over and over in a loop, with the loop indexes in the template arguments. Like this (except not illegal):
template <typename T, int k>
T function(T x) {
for (int i = 1; i <= k; ++i) {
for (int j = i - 1; j >= 0; --j) {
constexpr bool method_one_ok = function_of(i, j);
if (method_one_ok) {
x = method_one<T, i, j, k>(x);
} else {
x = method_two<T, i, j, k>(x);
}
}
}
return x;
}
I know how to hobble through it with recursive templates and specializations, but that's a horror I figured out around about C++11 or earlier.
Is there a cleaner way to do this?
Here's a handy function:
template<typename T, T... I, typename F>
constexpr void idx_for_each(F&& f, std::integer_sequence<T, I...>) {
(static_cast<void>(f(std::integral_constant<T, I>{})), ...);
}
It will call a function with every integer in an integer_sequence
wrapped in an integral_constant
. For example:
idx_for_each(f, std::make_index_sequence<3>{})
// Calls
f(std::integral_constant<std::size_t, 0>{});
f(std::integral_constant<std::size_t, 1>{});
f(std::integral_constant<std::size_t, 2>{});
Then with some clever transformations from 0 -> X
to your desired ranges 1 -> k+1
and i-1 -> -1
, you can write:
template <typename T, int k>
T function(T x) {
idx_for_each([&](auto ii) {
constexpr int i = ii + 1;
idx_for_each([&](auto jj) {
constexpr int j = i - jj - 1;
constexpr bool method_one_ok = function_of(i, j);
if (method_one_ok) {
x = method_one<T, i, j, k>(x);
} else {
x = method_two<T, i, j, k>(x);
}
}, std::make_integer_sequence<int, i>{});
}, std::make_integer_sequence<int, k>{});
return x;
}
This can be made to work in C++14 by replacing the fold expression:
template<typename T, T... I, typename F>
constexpr void idx_for_each(F f, std::integer_sequence<T, I...>) {
// (static_cast<void>(f(std::integral_constant<T, I>{})), ...);
using consume = int[];
static_cast<void>(consume{ (static_cast<void>(f(std::integral_constant<T, I>{})), 0)... });
}
And can be made C++11 by implementing std::integer_sequence
/std::make_integer_sequence
for C++11.
You can make this easier for your specific case by having a helper range<start, stop>
that is an integer sequence from start to stop directly so you don't have to manipulate the function argument.
In general, "template loops" are implemented by creating a new pack with std::index_sequence
and applying it to a function, and folding over that pack.
The C++20 solution is to do this totally in line with a lambda with a template parameter list:
template <typename T, int k>
T function(T x) {
[&]<int... ii>(std::integer_sequence<int, ii...>) {
([&]{
constexpr int i = ii + 1;
[&]<int... jj>(std::integer_sequence<int, jj...>) {
([&]{
constexpr int j = i - jj - 1;
constexpr bool method_one_ok = function_of(i, j);
if constexpr (method_one_ok) {
x = method_one<T, i, j, k>(x);
} else {
x = method_two<T, i, j, k>(x);
}
}, ...);
}(std::make_integer_sequence<int, i>{});
}(), ...);
}(std::make_integer_sequence<int, k>{});
return x;
}