I would like to back-port the following code to C++11:
template<unsigned i>
static void bar() { /* some code with compile-time optimizations for each value i */ }
template <unsigned... I>
void f()
{
((bar<I>()),...);
}
The order of invocation of 'bar' for each value of the parameter pack I is important - which I believe is working in the C++17 implementation above because the fold-expression is using the comma operator.
I initially went for an explicitly recursive implementation:
template <unsigned... I>
typename std::enable_if<sizeof...(I) == 0>::type g() {}
template <unsigned head, unsigned... I>
void g()
{
bar<head>();
g<I...>();
}
Which seems to work but requires two implementations for g(). While trying to get down to a single function, I read that the pack expansion would happen in make_tuple and thought that this would work:
template<unsigned... I>
static void h1()
{
std::make_tuple( (bar<I>(), 0)... );
}
unfortunately, this won't provide any guaranty on the order of execution - in fact with gcc the execution order is exactly the inverse. Alternatively, I could use a braced-init-list:
template<unsigned... I>
static void h2()
{
using expand = int[];
expand{ 0, ( bar<I>(), 0) ... };
}
This seems to preserve the order with gcc but I couldn't really figure out if this is only a coincidence.
So, the specific questions are:
You are correct that your h1
does not guarantee evaluation order in any version of C++, since initializations of function parameters are indeterminately sequenced. Your h2
does guarantee evaluation order in C++11 and later. And in fact, any method which puts the calls as elements of a {
braced list }
will guarantee it.
All versions of C++ since C++11 contain the paragraph [dcl.init.list]/4:
Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions ([temp.variadic]), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list. [Note: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call. – end note]