处理元组中的右值引用

dealing with rvalue ref inside tuple

我想传递和转发包含右值引用的元组。但是我无法访问元组元素并以正确的方式进行转发。

在示例中,我还提供了一个具有命名元素的结构,而不是 std::tuple,效果很好。

所以我自己根本找不到答案如何写那一行:

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

这样我就得到了正确的转发类型。

完整示例:

#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
}

我知道我可以对元组本身使用转发引用。这可以正常工作,但在我的代码中是不可能的,因为我有意需要在其中进行一些数据复制,如果我通过右值引用转发元组本身,这将不起作用。如果我可以使用它,我可以简单地使用 make_from_tuple.

std::get returns 处理元组时对对象的引用。因此,即使它是右值引用,std::get 的 return 类型也将是左值引用。

要从元组中获取类型,请改用 std::tuple_element

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

编辑: 如果我理解你的评论是正确的,你应该能够通过别名元组类型来扩展它。

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)>{});
}

您的函数 func 复制元组内的右值,因为您的元组是常量,因此无法将它们移出:

// -----v
Y func( const std::tuple< PARMS... >& t ) 

调用get时,会return成员的引用,因为是常量元组,所以是常量引用,会被复制。

但是,std::get也做一些转发。例如,如果你发送一个 std::tuple<T>&std::get,它将 return 发送一个 T&。如果你想转发,在你的情况下你必须移动元组:

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

然后,作为奖励,您不需要转发,因为转发是由 std::get 完成的。