Reputation: 4411
Is there a good way to forward arguments of a function f
to a function g
in a situation like
template<typename... T>
void g(T && ... x);
template<typename... T>
void f(T && ... x)
{
g(x..., x...);
}
In the next code x
could be moved twice
template<typename... T>
void f(T && ... x)
{
g(std::forward<T>(x)..., std::forward<T>(x)...);
}
In the next code std::forward<T>(x)...
could be evaluated before x...
template<typename... T>
void f(T && ... x)
{
g(x..., std::forward<T>(x)...);
}
Upvotes: 2
Views: 350
Reputation: 392833
You can in general force the order using
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 .
Upvotes: 2
Reputation: 275230
std::forward
doesn't move things - it creates a reference that says "it is ok to move from me". The actual moving occurs inside g
, not in f
where std::forward
or std::move
is called.
The problem of move
is only one of the problems here. There is also the problem of passing the same object twice as a reference in two spots, which is generally considered quite rude!
We can fix that by creating temporary objects in f
and pass those by reference, but that leads to a serious issue: references are often used to return values from a function, and we have the same variable being used twice -- we cannot return both results.
So the answer is "don't do that", because it is not in general safe. You have to know the semantics of both g
and f
to figure out what the correct thing to do is, and a simple forwarding type interface won't reflect the depth of knowledge required.
If you do have deep semantic understanding of what g
and f
are supposed to do, then the situation changes.
Upvotes: 6