2

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)...);
}
a.lasram
  • 4,371
  • 1
  • 16
  • 24
  • [std::foward](http://en.cppreference.com/w/cpp/utility/forward)? – Borgleader Jul 16 '13 at 19:51
  • You can't pass multiple identical references to another function with at least one being an rvalue reference and not expect terrible things to happen. Take the parameters by `const&`. – Casey Jul 16 '13 at 19:56
  • If "g" is inlined than "yes". If G is a template, than it will be inlined if the compiler thinks it is a good idea. And the compiler is smarter than you (when it comes to inlining) so its usually best just to let it do its thang. – IdeaHat Jul 16 '13 at 20:02
  • Or std::move, that could also work if you are trying to, but whoa boy this is a bad idea. – IdeaHat Jul 16 '13 at 20:14
  • Sorry my question wasn't clear I'll edit – a.lasram Jul 16 '13 at 20:23
  • The right thing to do depends on what `g`s signature looks like. If `g` is `g( T... t )` it is different than if `g` is `g( T&... t )` or `g( T const&... t )` or `g( T&&... t )`. What is `g`? If you don't know, you shouldn't be writing `f`! – Yakk - Adam Nevraumont Jul 16 '13 at 20:25
  • @Yakk I've edited the question to make it clear (hopefully) – a.lasram Jul 16 '13 at 20:33
  • More interesting, I think I have 'found' a sane treatment of the problem! – sehe Jul 16 '13 at 21:02
  • 2
    What exactly is the problem with `g(x..., x...)`? You can't really do better than this, since moving is a problem in any case. – Xeo Jul 16 '13 at 21:07
  • @Xeo nothing is wrong with g(x..., x...) but it would've been nice if we were able to pass the first x... as an lvalue and pass the second x... via std::forward – a.lasram Jul 16 '13 at 22:15

2 Answers2

6

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • "references are often used to return values from a function" - wait, what? – sehe Jul 16 '13 at 20:41
  • `bool try_fill_vector( std::vector& fill_this )` -- the reference value `fill_this` is used to return values from the function. It isn't a "return value", but values are returned through reference parameters lots of the time. Now suppose we had `void split_vector( std::vector const& src, int split, std::vector& lower, std::vector& upper)` -- if we pass the same lvalue to both `lower` and `upper`, the functions behavior becomes seriously wonkey. – Yakk - Adam Nevraumont Jul 16 '13 at 21:09
  • I was seriously thrown off by the use of `return` :) I see what you mean now, the example was less necessary :/ – sehe Jul 16 '13 at 21:10
2

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?

Enter Indices?!

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

  1. 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...>; };
    }
    
  2. 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 .

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • If `g` moves from the first reference of something, though, the second reference will be messed up. The fundamental problem is that g is simply receiving an aliased argument for every argument, and cannot possibly safely operate on one without affecting another. – GManNickG Jul 16 '13 at 21:05
  • @GManNickG I think you missed the point of the tuple? About the aliasing: so is a function `foo(int&, int&)`. C++ assumes the programmer knows what he's doing. – sehe Jul 16 '13 at 21:08
  • What do you mean by _you can __duplicate__ the arguments passed to `f`_ please? – Maxim Egorushkin Jul 16 '13 at 21:21
  • @MaximYegorushkin it's what the OP asks for. Read the 7th line of code in the original post. – sehe Jul 16 '13 at 21:29
  • @sehe you are right, the OP would like to copy or clone the arg. – Maxim Egorushkin Jul 16 '13 at 21:37
  • You're forwarding the arguments to f_invoke_helper not g. as you say in PS the simplest is to just stick with g(x..., x...) – a.lasram Jul 16 '13 at 22:22
  • @a.lasram I beg you a pardon? Kindly verify as to what `f_invoke_helper` calls? And, yeah, "don't do this" has been the recurring theme here, I supposed you got that message. Nonetheless, here you have a method to do what you wanted, while moving the appropriate arguments when possible ***only once***. I thought you'd like it, since it _was your question_ after all. – sehe Jul 16 '13 at 22:57
  • @sehe Sorry my question wasn't clear enough and I didn't specify exactly what I wanted. What I wanted is achieving something similar to g(x..., std::forward(x)...); if the standard would've specified the evaluation order of the arguments to be from left to right. In your answer (which is well elaborated and I do like it) I see that the input to g through std::get is always an lvalue and calling g(x..., x...) would be a shortcut. I may be misinterpreting the answer though! – a.lasram Jul 17 '13 at 00:06
  • Ah sorry. I interpreted it to be something saner, because _"achieving something like `g(x..., std::forward(x)...)`"_ isn't useful, regardless of initialization order! It's UB to pass `g(x, std::move(x))` effectively with order LTR. My answer is logically equivalent to `g(x2=std::move(x), x2)`, which is exactly what you want AFAICT. Anyways, you can force the order, let me edit the answer – sehe Jul 17 '13 at 06:29
  • @a.lasram I provided some documentation on forcing the order using {} initialization. I also explain why my solution is the only way to avoid moving from rvalue-refs twice. Lastly, let me point out that using `g(x..., x...)` is not really the same, as this will _never_ move from rvalue refs, whereas my solution will move from them exactly once per argument. (Not saying this is useful, just saying it's not the same). Hope this helps. – sehe Jul 17 '13 at 07:02