Search code examples
c++templatesc++11tuplesperfect-forwarding

Perfect forwarding and std::tuple (or other templated class)


I have some difficulties with perfect forwarding.

Here is my current level of understanding : glue Template + rvalue reference + std::forward and a special magical mode get activated where template deduction rules have not the same meaning as usual, but are crafted to allow perfect forwarding. Example :

template <typename T>
void outer(T&& t)
{
   inner(std::forward<T>(t)); // perfect forwarding activated
}

But what happen if T is actually a templated class ? For example, how can I perfect forward a std::tuple ? If use a T&& as aboce I will lost all type information of the objects contained in the tuple.
However the following code can't work :

template <typename... Args>
void outer(std::tuple<Args...>&& t) 
{
   inner(std::forward<std::tuple<Args...>>(t));
   use_args_types_for_something_else<Args...>(); // I need to have Args available
}

int main()
{
   std::tuple<int, double, float> t(4, 5.0, 4.0f);
   outer(t);
}

Last gcc snapshot says :

error: cannot bind 'std::tuple<int, double, float> lvalue to
std::tuple<int, double, float>&&

So clearly, we are still in the general, non-template case where lvalue can't bind to rvalue reference. "Perfect forwading mode" is not activated

So I tried to be sneaky and pass my tuple as a template template :

template <
  typename... Args
  template <typename...> class T
>
void outer(T<Args...>&& t) 
{
   inner(std::forward<T<Args...>>(t));
   use_args_type_for_something_else<Args...>(); 
}

But I still get the same error.


Solution

  • Perfect forwarding works only if the type of the parameter is a template type for the function, so the only way to achieve perfect forwarding is like in your first example:

    template <typename T>
    void outer(T&& t)
    {
       inner(std::forward<T>(t)); // perfect forwarding activated
    }
    

    The above works because it is a special case where T is deduced as SomeType& or SomeType&&.

    This, however, does not mean that the type information for the tuple elements is lost for good. It is still retrievable (although I don't think you can typedef a variadic template pack). For example, you can still call use_args_types_for_something_else like this:

    template <class T>
    struct call_uses_args;
    
    template <class ...Args>
    struct call_uses_args<std::tuple<Args...>>
    {
        void call() const { use_args_types_for_something_else<Args...>(); }
    };
    
    template <typename TupleT>
    void outer(TupleT&& t)
    {
       inner(std::forward<TupleT>(t));
       call_uses_args<typename std::remove_reference<TupleT>::type>().call();
    }
    

    There might be no good general solution, though, but hopefully such situations are rare. (E.g, in this particular example, it might be simpler just to overload outer.)