Связь между 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 &ri1decltype: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()является значением lvalue
T&&, выражение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Тип:
decTisTif LXR = prvalue
decTisT&&if LXR = xvalue
decTisT&if LXR = lvalue
EXPR_DEC_TYPEвозвращаетdecT, который совпадает с объявленным типом возврата.
Таким образом, в целом
- Если
initializerявляется массивом, тоdecltype(x)илиdecltype((x))не работайте просто над этим. Однакоautoбудет выведено на указатель.- Если
initializerявляется функцией, то применениеdecltype(fp)будет выведите к типу функции однако,autoвыведет к его возвращаемый тип.autoне может рассматриваться как замена какой-либо версииdecltype(), которую вы спросили.