Search code examples
c++c++17perfect-forwardingstdtuple

dealing with rvalue ref inside tuple


I want to pass and forward tuples which contain rvalue ref. But I am not able to access the tuple element and do forwarding in the correct way.

In the example I provide also a structure with named elements instead of a std::tuple which works well.

So I simply can't find the answer myself how to write that line:

return Y{ std::forward<decltype(std::get<0>(t))>( std::get<0>( t ) ) };

so that I got the correct type forwarded.

Full example:

#include <iostream>
#include <tuple>

class X
{   
    private:
        int data;
    public:
        X( int _data ): data{_data}{}
};  


class Y
{   
    public:
        Y( const X&  ) { std::cout << "copy" << std::endl; }
        Y(       X&& ) { std::cout << "move" << std::endl; } 
};  

struct Sm { X&& x; };
struct Sc {  X& x; };

template < typename AnyS >
Y func( const AnyS& s ) 
{   
    return Y{ std::forward<decltype(s.x)>(s.x) };
}   

template < typename ... PARMS >
Y func( const std::tuple< PARMS... >& t ) 
{   
    return Y{ std::forward<decltype(std::get<0>(t))>( std::get<0>( t ) ) };
}

int main()
{
    Sm sm{ X{1} };
    Y ym = func( sm ); // should move, fine works
        
    X x{1};
    Sc sc{ x };
    Y yc = func( sc ); // should copy, fine works

    Y ytm = func( std::tuple< X& >{ x }); // should copy, works 
    Y ytc = func( std::tuple< X&& >{ X{1} }); // should move but copies! FAIL
}

I know that I can use forwarding reference for the tuple itself. That will work fine but is not possible inside my code as I intentionally need to have some data duplication inside it which will not work if I forward the tuple itself by rvalue reference. If I could use it, I simple can use make_from_tuple.


Solution

  • std::get returns a reference to the object when dealing with a tuple. So even if it's a rvalue-ref the return type of std::get will be a lvalue-ref.

    To get the type from the tuple, use std::tuple_element instead.

    return Y{ std::forward<std::tuple_element_t<0, std::tuple<PARMS...>>>( std::get<0>( t ) ) };
    

    Edit: If I understand you comment correctly, you should be able to expand it by aliasing the tuple type.

    template <typename ... PARMS, std::size_t... Is>
    Y func(const std::tuple<PARMS...>& t, std::index_sequence<Is...>)
    {
        using tt = std::tuple<PARMS...>;
        return (Y{ std::forward<std::tuple_element_t<Is, tt>>( std::get<Is>( t ) ) }, ...);
    }
    
    template < typename ... PARMS, int... Is >
    Y func( const std::tuple< PARMS... >& t ) 
    {
        return func(t, std::make_index_sequence<sizeof...(PARMS)>{});
    }