Связь между auto и decltype
Есть
auto x = initializer;
Эквивалентно
decltype(initializer) x = initializer;
Или
decltype((initializer)) x = initializer;
Или нет?
5 ответов:
decltype
также рассматривается, является ли выражениеrvalue
илиlvalue
.Тип, обозначаемый decltype, может отличаться от типа, выводимого автоматически.
Это в значительной степени объясняет существенную разницу. Обратите внимание, что#include <vector> int main() { const std::vector<int> v(1); auto a = v[0]; // a has type int decltype(v[0]) b = 1; // b has type const int&, the return type of // std::vector<int>::operator[](size_type) const auto c = 0; // c has type int auto d = c; // d has type int decltype(c) e; // e has type int, the type of the entity named by c decltype((c)) f = c; // f has type int&, because (c) is an lvalue decltype(0) g; // g has type int, because 0 is an rvalue }
decltype(c)
иdecltype((c))
не одно и то же!И иногда
auto
иdecltype
работают вместе в кооперативном порядке, как в следующем примере (взятом из wiki , и немного изменено):int& foo(int& i); float foo(float& f); template <class T> auto f(T& t) −> decltype(foo(t)) { return foo(t); }
Википедия далее объясняет семантику
decltype
следующим образом:Подобно оператору sizeof, операнд decltype не вычисляется. Неофициально тип, возвращаемый decltype (e), выводится следующим образом:
- Если выражение e ссылается на переменную в локальной области или пространстве имен, статическую переменную-член или параметр функции, то результатом является объявленный тип этой переменной или параметра
- Если e является вызовом функции или перегруженным вызовом оператора, decltype (e) обозначает объявленный тип возврата этой функции
- В противном случае, если e-lvalue, decltype (e) - T&, где T-тип e; если e-rvalue, результат T
Эти семантики были разработаны для удовлетворения потребностей авторов универсальных библиотек, в то же время будучи интуитивно понятными для начинающих программистов, поскольку возвращаемый тип decltype всегда соответствует типу объекта или функция точно такая, как заявлено в исходном коде. Более формально Правило 1 применяется к непарентизированным id-выражениям и выражениям доступа к членам класса. Для вызовов функций выводимый тип является возвращаемым типом статически выбранной функции, как определено правилами разрешения перегрузки. Пример:
const int&& foo(); int i; struct A { double x; }; const A* a = new A(); decltype(foo()) x1; // type is const int&& decltype(i) x2; // type is int decltype(a->x) x3; // type is double decltype((a->x)) x4; // type is const double&
Причина различия между двумя последними вызовами decltype заключается в том, что заключенное в скобки выражение (a - >x) не является ни id-выражением, ни a выражение доступа к члену, и поэтому не обозначает именованный объект.Поскольку выражение является значением lvalue, его выводимый тип - "ссылка на тип выражения", или const double&.
Это не сработает (и некрасиво):
decltype([]() { foo(); }) f = []() { foo(); };
Тогда как
auto f = []() { foo(); };
Воля.
Это зависит от обстоятельств.
auto
иdecltype
служат разным целям, поэтому они не сопоставляются один к одному.Правила для
auto
проще всего объяснить, потому что они такие же, как и для вывода параметров шаблона. Я не буду распространяться о них здесь, но обратите внимание, чтоauto&
иauto&&
также являются некоторыми возможными вариантами использования!
decltype
однако имеет несколько случаев, некоторые из которых вы проиллюстрировали выше (информация и цитаты взяты из n3290, 7.1.6.2 простые спецификаторы типов [dcl.тип.просто]) что я отделяю на две категории:Неофициально я бы сказал, что
- при использовании того, что стандарт называет "unparenthesized id-expression или unparenthesized class member access"
- остальное!
decltype
может оперировать либо именами (для первого случая), либо выражениями. (Формально и в соответствии с грамматикойdecltype
оперирует выражениями, поэтому думайте о первом случае как об уточнении, а о втором случае как о уловке.)При использовании имени с помощью decltype вы получаете объявленный тип этой сущности. Так, например,
decltype(an_object.a_member)
- это тип члена, как он появляется в определении класса. С другой стороны, если мы используемdecltype( (an_object.a_member) )
, мы попадаем в общий случай, и мы проверяем тип выражения , как он будет выглядеть в коде.Соответственно, как охватить все случаи ваших вопросов:
int initializer; auto x = initializer; // type int // equivalent since initializer was declared as int decltype(initializer) y = initializer;
enum E { initializer }; auto x = initializer; // type E // equivalent because the expression is a prvalue of type E decltype( (initializer) ) y = initializer;
struct { int const& ializer; } init { 0 }; auto x = init.ializer; // type int // not equivalent because declared type is int const& // decltype(init.ializer) y = init.ializer; // not equivalent because the expression is an lvalue of type int const& // decltype( (init.ializer) ) y = init.ializer;
auto
auto
это просто: он даст тот же тип, что и вычет параметра шаблона по значению.auto
работает равномерно на выражениях.template <class T> void deduce(T x); int &refint(); std::string str(); std::string const conststr(); auto i1 = 1; // deduce(1) gives T=int so int i1 auto i2 = i1; // deduce(i1) gives T=int so int i2 auto i3 = refint(); // deduce(refint()) gives T=int so int i3 const auto ci1 = i1; // deduce(i1) gives T=int so const int ci1 auto i4 = ci1; // deduce(ci1) gives T=int so int i4 auto s1 = std::string(); // std::string s1 auto s2 = str(); // std::string s2 auto s3 = conststr(); // std::string s3
В C++ выражения не могут иметь ссылочного типа (
Обратите внимание, что ценность выражения не является проблемой для выражения справа (справа знака равенства или чего-то, что копируется вообще). Rvaluerefint()
имеет типint
неint&
).1
обрабатывается как lvaluesi1
иrefint()
.Для параметров по значению (то есть не ссылочных параметров) применяется не только преобразование lvalue в rvalue, но и преобразование массива в указатель. модификатор игнорируется.
decltype
decltype
это очень полезная функция с ужасным интерфейсом:
decltype
действует по-разному на некоторые выражения, определенные в термине поиска имени и других выражениях! Это тип функций, которые заставляют людей ненавидеть C++.
decltype
чего-то названный
decltype(entity)
сделает поиск имени и даст объявленный тип сущности. (entity
может быть неквалифицированным или квалифицированным идентификатором или доступом к члену, таким какexpr.identifier
.)
decltype(f(args))
сделает поиск имени и разрешение перегрузки и даст объявленный возвращаемый тип функции, а не тип выражения:Так что теперь я могу проверить свое понимание языка с помощьюextern decltype(refint()) ri1; // int &ri1
decltype
:template <class T, class U> struct sametype {}; template <class T> struct sametype<T,T> {typedef int same;};
sametype<T,U>::same
существует iffT
иU
точно такие же тип.sametype<decltype (i1), int>::same check_i1; sametype<decltype (i2), int>::same check_i2; sametype<decltype (i3), int>::same check_i3; sametype<decltype (i4), int>::same check_i4; sametype<decltype (ci1), const int>::same check_ci1; sametype<decltype (ir1), int&>::same check_ir1; sametype<decltype (s1), std::string>::same check_s1; sametype<decltype (s2), std::string>::same check_s2; sametype<decltype (s3), std::string>::same check_s3;
Компилирует отлично , так что я не ошибся!
decltype
других выраженийВ противном случае, где
expr
не определяется в термине поиска имени (ни в одном из приведенных выше случаев), например выражение(expr)
,decltype
реализует отдельную функцию (но разработчики C++ не стали бы тратить на нее еще одно ключевое слово).)
decltype(expr)
даст вид выражения, украшенного его lxrvalueness (lvalue / xvalue / prvalue-ness):
- prvalue (pure rvalue) типа
T
даетT
- xvalue типа
T
даетT&&
- lvalue типа
T
даетT&
Это обратная функция правила вызова функции: Если
f
является функцией с возвращаемым типом
T&
, выражениеf()
является значением lvalueT&&
, выражениеf()
является xvalue- голый тип (чистый тип объекта, без ссылки)
T
, выражениеf()
является prvalueА также для слепков: для голого типа (чистый тип объекта, без ссылки)
T
(T&)expr
является lvalue(T&&)expr
является xvalue(T)expr
является prvalueРеферентность-это кодирование lxrvalueness в типы.
decltype
делает это кодирование для сохранения и передачи lxrvalueness вещей.Это полезно, когда требуется псевдоним выражения: lxrvalueness объекта выражение
expr
, которое является вызовом функции (либо обычнымf(args)
, либо с синтаксисом оператора, подобнымa @ b
), совпадает с lxrvaluenessalias()
, объявленным какdecltype(expr) alias();
Это может быть использовано для чистой пересылки в общем коде:
// decorated type of an expression #define EXPR_DEC_TYPE(expr) decltype((expr)) int i; int &ri = i; int fi(); int &fri(); EXPR_DEC_TYPE(i) alias_i = i; // int & EXPR_DEC_TYPE(ri) alias_ri = ri; // int & EXPR_DEC_TYPE(fi()) alias_fi(); // int alias_fi() EXPR_DEC_TYPE(fri()) alias_fri(); // int &alias_fri()
Обратите внимание, что
EXPR_DEC_TYPE(foo())
равноdeclexpr(foo())
по конструкции (в большинстве случаев), но вычисления различны:
declexpr(foo(args))
делает поиск имени дляfoo
, делает перегрузку разрешение, находит объявление, возвращает точный тип возвращаемого значения объявленный, конец истории
EXPR_DEC_TYPE(foo(args))
затем находит тип объявления вычисляет
Тип
T
выражения, являющегося возвращаемым типом naked (без
ссылка)Lxrvalueness LXR выражения в соответствии с референсностью объявленный тип возврата: именующее для справки, ориентированы на Р-справочник...
Затем он украшает тип
T
С LXR , чтобы получитьdecT
Тип:
decT
isT
if LXR = prvaluedecT
isT&&
if LXR = xvaluedecT
isT&
if LXR = lvalue
EXPR_DEC_TYPE
возвращаетdecT
, который совпадает с объявленным типом возврата.
Таким образом, в целом
- Если
initializer
является массивом, тоdecltype(x)
илиdecltype((x))
не работайте просто над этим. Однакоauto
будет выведено на указатель.- Если
initializer
является функцией, то применениеdecltype(fp)
будет выведите к типу функции однако,auto
выведет к его возвращаемый тип.auto
не может рассматриваться как замена какой-либо версииdecltype()
, которую вы спросили.