Reputation: 25613
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.
Upvotes: 4
Views: 618
Reputation: 12928
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)>{});
}
Upvotes: 4
Reputation: 41780
Your function func
copies the rvalues inside the tuple because they cannot be moved out since your tuple is const:
// -----v
Y func( const std::tuple< PARMS... >& t )
When calling get, it will return a reference to the member, and since it's a constant tuple, it will be a constant reference, which will be copied.
But, std::get
also do some kind of forwarding. For example, if you send a std::tuple<T>&
to std::get
, it will return a T&
. If you want forwarding, you will in your case have to move the tuple:
template<typename ... PARMS>
Y func(std::tuple<PARMS...> t)
{
return Y{std::get<0>(std::move(t))};
}
Then, as a bonus, you don't need the forward since the forwarding is done by std::get
.
Upvotes: 1