Recently I found this StackOverflow answer about unrolling a loop with templates. The answer states that "the idea is applicable to C 11", and I ended up with this:
namespace tmpl {
namespace details {
template<class T, T... values>
class integer_sequence {
public:
static constexpr size_t size() { return sizeof...(values); }
};
template<class T, class N, class... Is>
struct make_integer_sequence_helper :
make_integer_sequence_helper<T, std::integral_constant<T, N::value - 1>, std::integral_constant<T, N::value - 1>, Is...> {};
template<class T, class... Is>
struct make_integer_sequence_helper<T, std::integral_constant<T, 0>, Is...> {
using type = integer_sequence<T, Is::value...>;
};
template<class T, T N>
using make_integer_sequence = typename make_integer_sequence_helper<T, std::integral_constant<T, N>>::type;
template<class... Ts>
void variadic_noop(Ts... params) {}
template<class F, class T>
int call_and_return_0(F&& f, T i) {f(i); return 0;}
template<class T, T... Is, class F>
void loop(integer_sequence<T, Is...>, F&& f) {
variadic_noop(call_and_return_0(f, Is)...);
}
}
template<class T, T max, class F>
void loop(F&& f) {
details::loop(details::make_integer_sequence<T, max>{}, f);
}
}
Let's take a simple example of how this template would be used:
tmpl::loop<size_t, 20>([&](size_t idx) {
cout << "Loop " << idx << std::endl;
});
When I use the C 17 code from the other answer, it iterates from 0 up to 19. However, the C 11 jank I've written iterates from 19 down to 0.
In theory, when details::loop()
is expanded it should become something like this:
variadic_noop(call_and_return_0(f, 0), call_and_return_0(f, 1), call_and_return_0(f, 2), ...);
So, why does C run call_and_return_0(f, 19)
first if it's the last parameter to variadic_noop()
?
CodePudding user response:
The evaluation of function arguments in a function call may be from left to right, right to left, or any other order, which is not required to be predictable.
However, when you have a single expression of the form e_1, e_2, ..., e_n
where the subexpressions e_1
, e_2
, ..., e_n
are separated by comma operators, then the subexpressions will always be evaluated in left-to-right order, because the comma operator guarantees that its left operand is evaluated before its right operand. (Prior to C 17, this guarantee only holds for the built-in comma operator, and not for any overloaded ones.)
Although function calls also use commas, the commas in a function call do not enforce left-to-right evaluation. The code from the linked answer uses C 17 fold expressions to create a sequence of expressions separated by comma operators.
CodePudding user response:
Adding onto what Brian said, the only way to get the order reliably is to write the loop as a recursive template. Sample implementation:
template <class T, T I, T... Is, class F>
void loop(integer_sequence<T, I, Is...>, F&& f) {
f(I);
loop(integer_sequence<T, Is...> {}, f);
}
template<class T, class F>
void loop(integer_sequence<T>, F&& f) {}