Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Order of function calls in variadic template expansion

I've found code in an open source project that basically looks like this:

template< typename... Args >
void expand_calls_hack(Args&&... args)
{}

template <unsigned int... Indices>
struct foo
{
  static void bar(some_tuple_type& t)
  {
    meta::expand_calls_hack((std::get<Indices>(t).doSomething(), 0)...);
  }
};

I figured this "construct" is used to call doSomething() for each of the tuple elements. However, it seems to me that the order of the calls to doSomething() is undefined, at least it would be with normal functions in C++03. This would be a bug, since the calls have side effects. I have two questions about this:

  1. What is the use of the (tupleElement.doSomething(), 0) - Is that the comma operator? I gather that it has something to do with using expand_calls_hack to expand the calls.

  2. How can this be fixed so that the calls are evaluated left-to-right? Note that I need that to compile on VC2013. I've tried expanding a list of lambdas and calling it sequentially, but I could not get that to compile.

I hope I've not left out too much context, but for the curious, the source of this code is on github here, line 419

like image 665
ltjax Avatar asked Sep 28 '22 11:09

ltjax


1 Answers

The first part of the question is quite simple: std::get<I>(t).doSomething() may return void. As a result, you can't directly use this expression as an argument to expand_calls_hack(). Using the comma operator arranges for an argument to be present even if doSomething() returns void.

The order of function evaluations when invoking a variadic function template is not special, i.e., the evaluation order is undefined. However, it turns out that the order of evaluation of constructor arguments when using brace initialization is defined (see, e.g., this question). That is, you can guarantee the order of evaluation using something like this:

namespace meta {
    struct order {
        template <typename... T>
        order(T&&...) {}
    };
}
// ...
template <unsigned int... Indices>
struct foo
{
    template <typename T>
    static void bar(T&& t)
    {
        meta::expand_calls_hack((std::get<Indices>(t).doSomething(), 0)...);
        meta::order{(std::get<Indices>(t).doSomething(), 0)...};
    }
};

In the above function the use of expand_calls_hack() evaluates the expressions back to front on my system while the use of order evaluates them front to back as it has to. I think a similar effect can be achieved using an std::initializer_list<int> which would have the need effect of not requiring a variadic template: the arguments are all ints anyway:

namespace meta {
    struct init_order {
        init_order(std::initializer_list<int>) {}
    };
}

It would be used exactly the same as meta::order.

like image 190
Dietmar Kühl Avatar answered Oct 04 '22 23:10

Dietmar Kühl