10

Consider the following code:

class Widget{};

template<typename T>
T &&foo2(T &&t){
    return std::forward<T>( t );
}

/// Return 1st element
template<typename T>
typename std::tuple_element<0, typename std::decay<T>::type >::type  &&foo(T &&t){
    return std::forward< typename std::tuple_element<0, typename std::decay<T>::type >::type >
            ( std::get<0>(t) );
}

Widget w;
auto list = std::make_tuple(
    w,
    Widget()
);


int main()
{
  auto &l  = foo(list );                      // This is NOT work
  //auto &l2 = foo2( std::get<0>(list) );     // This one works.
}

http://coliru.stacked-crooked.com/a/4d3b74ca6f043e45

When I tried to compile this I got the following error:

error: invalid initialization of non-const reference of type 'Widget&' from an rvalue of type 'std::tuple_element<0ul, std::tuple<Widget, Widget> >::type {aka Widget}'

Well, and that would be ok, but:

  • at first, that Widget w is not temporary. Why it treat it like temporary?

  • at second, why foo2 works than?

P.S. As you see, I try to write function which operates both with lvalue and rvalue. If first element is temporary I want to return rvalue, if it is not - lvalue.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
tower120
  • 5,007
  • 6
  • 40
  • 88
  • Are you aware that the type of `std::make_tuple(w, Widget())` is `std::tuple` and not `std::tuple`? If you want to deduce a tuple of reference types you need [`std::forward_as_tuple(w, Widget())`](http://en.cppreference.com/w/cpp/utility/tuple/forward_as_tuple) which will return `std::tuple`. Or is the real problem here that you want a function that creates a tuple with an lvalue references for lvalue reference inputs, and a plain value for rvalue reference inputs? – Casey Jun 06 '14 at 14:57
  • I need a function that accepts both forward_as_tuple and make_tuple. And returns rvalue reference in first case, an lvalue reference in second (return value is one of tuple elements). – tower120 Jun 06 '14 at 15:45

3 Answers3

5

tuple_element returns the element type, not a reference type (unless the element type is itself a reference type).

You need to have it return a reference type if the type T is a reference type.

This can be expressed with a conditional:

typename std::conditional<std::is_lvalue_reference<T>::value,
    typename std::add_lvalue_reference<
        typename std::tuple_element<0, typename std::decay<T>::type >::type>::type,
    typename std::tuple_element<0, typename std::decay<T>::type >::type>::type

Or, more easily, using decltype, since std::get already performs this calculation for you:

decltype(std::get<0>(std::declval<T &&>())) &&
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Should I, still, add `std::forward` to return value, while using `decltype(std::get<0>(std::declval())) &&` ? – tower120 Jun 06 '14 at 14:47
2

You could do this much simpler:

template<typename T>
auto foo(T &&t) -> decltype(std::get<0>(std::forward<T>(t))) {
    return std::get<0>(t);
}
101010
  • 41,839
  • 11
  • 94
  • 168
0

foo returns an rvalue reference so you can't bind it to an auto& because that requires an lvalue reference.

foo2 uses a "universal reference" that evaluates to an lvalue reference in this case because std::get returns an lvalue reference and you perfectly forward it to the return value.

isarandi
  • 3,120
  • 25
  • 35
  • Can I change `foo` so it return "universal reference" too ? – tower120 Jun 06 '14 at 14:22
  • Universal reference is not an actual thing, it just means that we the parameter can have either an lvalue reference and rvalue reference type depending on template instantiation (which depends on the supplied argument at the call site). What you want is in 40two's answer, but make sure to forward the parameter `t`. – isarandi Jun 06 '14 at 16:20