Почему std::declval добавляет ссылку?

std::declval — это утилита времени компиляции, используемая для создания выражения с целью определения его типа. Он определяется следующим образом:

template< class T >
typename std::add_rvalue_reference<T>::type declval() noexcept;

Не будет ли это проще?

template< class T >
T declval() noexcept;

В чем преимущество ссылочного возвращаемого типа? И разве это не должно называться declref?

Самый ранний исторический пример, который я нашел, это n2958, который вызывает функцию value(), но уже всегда возвращает ссылку.

Обратите внимание, что операнд decltype не обязательно должен иметь доступный деструктор, т. е. он не проверяется семантически как полное выражение.

template< typename t >
t declprval() noexcept;

class c { ~ c (); };
decltype ( declprval< c >() ) * p = nullptr; // OK

person Potatoswatter    schedule 07.09.2014    source источник
comment
Разве T не нужно копировать, чтобы T был допустимым типом возвращаемого значения? T && избегал бы этого.   -  person user2357112 supports Monica    schedule 07.09.2014
comment
Я думаю, что это для поддержки типов, которые не могут быть возвращены по значению, например. тип функции, тип массива и абстрактный класс.   -  person Jamboree    schedule 07.09.2014
comment
@ user2357112: Подвижности достаточно.   -  person user541686    schedule 07.09.2014
comment
@Mehrdad Любой тип класса может быть типом возвращаемого значения, подвижность не требуется.   -  person Potatoswatter    schedule 07.09.2014
comment
@jrok Мой пример также не требует конструктивности.   -  person Potatoswatter    schedule 07.09.2014
comment
@Potatoswatter: я никогда не говорил, что подвижность необходима.   -  person user541686    schedule 07.09.2014
comment
@Mehrdad Технически верно, но…   -  person Potatoswatter    schedule 07.09.2014


Ответы (3)


Правило «не вводится временное значение для функции, возвращающей prvalue типа объекта в decltype» применяется только в том случае, если сам вызов функции является либо операндом decltype, либо правым операндом оператора запятой, который является операндом decltype (§5.2.2 [expr .call]/p11), что означает, что с учетом declprval в OP,

template< typename t >
t declprval() noexcept;

class c { ~ c (); };

int f(c &&);

decltype(f(declprval<c>())) i;  // error: inaccessible destructor

не компилируется. В более общем случае возврат T предотвратит наиболее нетривиальное использование declval с неполными типами, типами с приватными деструкторами и т.п.:

class D;

int f(D &&);

decltype(f(declprval<D>())) i2;  // doesn't compile. D must be a complete type

и от этого мало пользы, поскольку значения x в значительной степени неотличимы от значений pr, за исключением случаев, когда вы используете для них decltype, и вы обычно не используете decltype непосредственно для возвращаемого значения declval - вы уже знаете тип.

person T.C.    schedule 07.09.2014
comment
Ах, я не знал, что непроверенный деструктор был особым исключением. Мой пример был совершенно ошибочным. - person Potatoswatter; 07.09.2014
comment
для функции, возвращающей значение prvalue типа объекта в decltype, временное значение не введено превращается в Если выражение является значением prvalue, отличным от непосредственного вызова (возможно, заключенного в скобки) (начиная с C+ +20), из этого prvalue не материализуется временный объект. начиная с c++17. en.cppreference.com/w/cpp/language/decltype - person 陳 力; 23.11.2018

Массивы не могут быть возвращены по значению, поэтому даже просто объявление функции, возвращающей массив по значению, является недопустимым кодом.

Однако вы можете вернуть массив по ссылке.

person 6502    schedule 07.09.2014
comment
Это неправда, T не обязательно должен быть копируемым или перемещаемым, он даже может быть неполным, см. coliru.stacked-crooked.com/a/3f387e9e4e0ce190 - person Jamboree; 07.09.2014
comment
@Jamboree: использование typedef int A[4] сделает A f(); недействительным кодом. - person 6502; 07.09.2014
comment
Я не понимаю, какое это имеет отношение к declval. - person jrok; 07.09.2014
comment
@jrok: определение declval на основе функции, возвращающей T по значению, нельзя использовать, когда T является массивом. - person 6502; 07.09.2014
comment
Я знаю. Но я не знаю, почему вы когда-либо хотели использовать declval с типами массивов (может быть, это просто недостаток воображения). - person jrok; 07.09.2014

Назначение decltype() состоит в том, чтобы иметь выражение, которое действует как допустимое значение типа T, чтобы поместить его как T в выражениях, ожидающих Ts. Проблема в том, что в C++ тип T может быть не копируемым или даже не конструируемым по умолчанию. Поэтому использование T{} для этой цели не работает.

Что делает decltype(), так это возвращает ссылку rvalue на T. Ссылка rvalue должна быть действительной для любого типа T, поэтому гарантируется, что у нас есть допустимая T из ссылки rvalue T, и гарантируется, что мы можем иметь ссылку rvalue для любого типа T. Это трюк.

Думайте о decltype() как о "дайте мне допустимое выражение типа T". Конечно, его использование предназначено для разрешения перегрузки, определения типа и т. д.; поскольку его целью является возврат действительного выражения (в синтаксическом смысле), а не возврата значения. Это отражается в том факте, что std::declval() вообще не определено, а только объявлено.
Если оно было определено, у нас снова возникает исходная проблема (мы должны сконструировать значение для произвольного типа T, а это невозможно).

person Manu343726    schedule 07.09.2014
comment
1. Я не предлагал использовать T{}. 2. decltype возвращает ссылку rvalue или lvalue. - person Potatoswatter; 07.09.2014