0

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:

  • will the implementation of h2 guaranty the correct execution order?
  • are there alternative implementations that I missed?
Come Raczy
  • 1,590
  • 17
  • 26
  • Your `using expand = int[];` code is one I commonly saw in C++11 code, and one I've used myself. I don't know if there's a name for the pattern. – aschepler Mar 12 '20 at 01:02
  • @aschepler do you know if the order of evaluation is guaranteed to be preserved? If so, I'd be very happy with the pointer to the meaningful part of the standard or any other c++11 reference document (and that should be an answer instead of a comment). – Come Raczy Mar 13 '20 at 16:55

1 Answers1

1

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]

aschepler
  • 70,891
  • 9
  • 107
  • 161