Неконстантная ссылка типа из rvalue

Рассмотрим следующий код:

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

Когда я попытался скомпилировать это, я получил следующую ошибку:

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

Ну, и это будет нормально, но:

  • сначала, что Widget w не является временным. Почему он относится к нему как временный?

  • во-вторых, почему foo2 работает, чем?

P.S. Как вы видите, я пытаюсь написать функцию, которая работает как с lvalue, так и с rvalue. Если первый элемент является временным, я хочу вернуть rvalue, если он не равен lvalue.

Ответ 1

tuple_element возвращает тип элемента, а не ссылочный тип (если только тип элемента не является ссылочным типом).

Вам нужно, чтобы он возвращал ссылочный тип, если тип T является ссылочным типом.

Это можно выразить условным:

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

Или проще, используя decltype, так как std::get уже выполняет этот расчет для вас:

decltype(std::get<0>(std::declval<T &&>())) &&

Ответ 2

Вы можете сделать это намного проще:

template<typename T>
auto foo(T &&t) -> decltype(std::get<0>(std::forward<T>(t))) {
    return std::get<0>(t);
}

Ответ 3

foo возвращает ссылку rvalue, поэтому вы не можете привязать ее к auto&, потому что для этого требуется ссылка lvalue.

foo2 использует "универсальную ссылку", которая оценивает ссылку на lvalue в этом случае, потому что std:: get возвращает ссылку lvalue и вы отлично перенаправляете ее на возвращаемое значение.