I am trying to write a general static_for
implementation that can accept bounds, an increment function & a comparison function to run a loop through. I have been using this construct with simple loops that increment by 1. In that case it is easy to stop the loop unrolling by simply specializing on the IDX & END
being equal.
However when the increment could be with an arbitrary integer, it is not guaranteed that the IDX & END
will always be equal. The if
conditional is only evaluated at run time. In the code snippet below I was trying to specialize on the std::false_type
which stops the recursion. The integral_constant is constructed by evaluating the std::less
functional (which could be substituted by the user for any other evaluation). Unfortunately this comparator
functional is also evaluated only at run time and therefore the compiler fails. Could someone advise on how to get this to work?
NOTE: Using C++11.
template <int idx, int end, typename eval, int count, typename comparator>
struct static_for_loop {
template <typename Lambda, typename... Args>
void operator()(const Lambda& function, Args... args) const {
if (comparator()(idx, end)) {
std::integral_constant<int, idx> i;
function(i, args...);
constexpr bool lesser = comparator()(idx + count, end);
static_for_loop<idx + count, end, std::integral_constant<bool, lesser>, count,
comparator>()(function, args...);
}
}
};
template <int idx, int end, int count, typename comparator>
struct static_for_loop<idx, end, std::false_type, count, comparator> {
template <typename Lambda, typename... Args>
void operator()(const Lambda& function, Args... args) const {}
};
template <int idx, int end, int count = 1, typename comparator = std::less<int>>
struct static_for {
template <typename Lambda, typename... Args>
void operator()(const Lambda& function, Args... args) const {
static_for_loop<idx, end, std::true_type, count, comparator>()(function, args...);
}
};
I find it easier to just wrap everything in an object:
template <int S, int E, int step>
struct iter {
auto next() { return iter<std::min(E, S+step), E, step>{}; }
};
And then you just have an overload for the case where it's done and the case where it's not:
template <int S, int E, int step, class F, class... Args>
void for_loop(iter<S, E, step> i, F func, Args... args) {
func(S, args...);
for_loop(i.next(), func, args...);
}
template <int E, int step, class F, class... Args>
void for_loop(iter<E, E, step>, F, Args... ) {
}
For instance:
// prints 0 4 8
for_loop(iter<0, 10, 4>{}, [](int i){std::cout << i << ' ';});
Alternatively, could use enable_if
to differentiate the cases to avoid the need for min
:
template <int S, int E, int step, class F, class... Args>
std::enable_if_t<(S<E)> for_loop(iter<S, E, step>, F func, Args... args)
{
func(S, args...);
for_loop(iter<S+step, E, step>{}, func, args...);
}
template <int S, int E, int step, class F, class... Args>
std::enable_if_t<!(S<E)> for_loop(iter<S, E, step>, F , Args... )
{
}
YMMV on which you prefer.
You can use sfinae to overcome the problem:
template <int idx, int end, typename eval, int count, typename Comparator>
struct static_for_loop {
template <typename Lambda, typename... Args>
auto operator()(Lambda&& function, Args&&... args) const
-> std::enable_if_t<Comparator{}(idx, end)> {
std::integral_constant<int, idx> i;
std::forward<Lambda>(function)(i, std::forward<Args>(args)...);
constexpr bool lesser = comparator{}(idx + count, end);
static_for_loop<
idx + count,
END,
std::integral_constant<bool, lesser>,
count,
Comparator
>()(std::forward<Lambda>(function), std::forward<Args>(args)...);
}
// do nothing when false
template <typename Lambda, typename... Args>
auto operator()(Lambda&& function, Args&&... args) const
-> std::enable_if_t<!Comparator{}(idx, end)> {
}
};
std::enable_if
will select the right function with sfinae. It will act as a compile time if.
I used perfect forwarding too, as your code didn't work in all case, like passing non copiable or a mutable lambda. Now it will.
If you do not have c++14, you can write typename std::enable_if<...>::type
instead.
Try to use less all uppercase name, it hurts lisibility.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With