Защо 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)


Правилото „не се въвежда временно за функция, връщаща prvavalue от обектен тип в 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

и това няма голяма полза, тъй като xvalues ​​са почти неразличими от prvalues, освен когато използвате decltype върху тях и обикновено не използвате decltype директно върху върнатата стойност на declval - вече знаете типа.

person T.C.    schedule 07.09.2014
comment
А, не разбрах, че непровереният деструктор е специално изключение. Примерът ми беше напълно погрешен. - person Potatoswatter; 07.09.2014
comment
не се въвежда временно за функция, връщаща prvavalue на обектен тип в decltype се превръща в Ако изразът е prvavalue, различно от (евентуално в скоби) незабавно извикване (тъй като 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