You can in general force the order using
- using separate statements (obviously)
- expressions separated by the comma operator. (Beware of overloaded
operator,
)
The use of brace initialization works because the order of evaluation of the arguments in a brace initializer list is the order in which they appear1. The following has well-defined evaluation order:
std::tuple<T..., T...> args {
std::forward<T>(x)...,
std::forward<T>(x)... }; // still not sane, but evaluation order defined
But it's still useless as g(...)
might still move from the same reference twice. What you'd actually want for rvalue refs is not:
g(rvalue, std::move(rvalue)); // or
g(std::move(rvalue), rvalue); // or even
g(std::move(rvalue), std::move(rvalue)); // [sic]
The only sane way would be:
g(lvalue=std::move(rvalue), lvalue); // BUT: fix the evaluation order
So how do we achieve precisely that but generically?
Let's say you have variadic g
as you described:
template<typename... T>
void g(T && ... x)
{
}
Now, you can duplicate the arguments passed to f
using
the index trick:
namespace detail // indices
{
template<std::size_t... Is> struct seq{};
template<std::size_t I, std::size_t... Is>
struct gen_seq : gen_seq<I-1, I-1, Is...>{};
template<std::size_t... Is>
struct gen_seq<0, Is...>{ using type = seq<Is...>; };
}
and an invoker helper function:
#include <tuple>
template<typename Tuple, std::size_t... Is>
void f_invoke_helper(Tuple const& tup, detail::seq<Is...>)
{
g(std::get<Is>(tup)..., std::get<Is>(tup)...);
}
All that's required next is to tie it all together:
template<typename... T>
void f(T && ... x)
{
f_invoke_helper(
std::make_tuple(std::forward<T>(x)...),
typename detail::gen_seq<sizeof...(T)>::type());
}
Note that if you pass rvalue-refs, it will get moved once (into the tuple) and used twice (as a lvalue) in the invoker helper:
int main()
{
std::string x = "Hello world";
int i = 42;
// no problem:
f(i, -42, std::move(x));
}
Hope this helps!
PS. As it has been aptly pointed out, it's probably a lot easier to just say
template<typename... T>
void f(T&&... x) { g(x..., x...); }
I haven't thought of a way in which the tuple idiom doesn't result in the same, except for actually moving movable arguments into the tuple.
1The semantics of T{...} are described in 12.6.1
See also: how to avoid undefined execution order for the constructors when using std::make_tuple .