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:
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.
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
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 int
s anyway:
namespace meta {
struct init_order {
init_order(std::initializer_list<int>) {}
};
}
It would be used exactly the same as meta::order
.
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