Связь между auto и decltype


Есть

auto x = initializer;

Эквивалентно

decltype(initializer) x = initializer;

Или

decltype((initializer)) x = initializer;

Или нет?

5 34

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++ выражения не могут иметь ссылочного типа (refint() имеет тип int не int&).

Обратите внимание, что ценность выражения не является проблемой для выражения справа (справа знака равенства или чего-то, что копируется вообще). Rvalue 1 обрабатывается как lvalues i1 и 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 существует iff T и 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), совпадает с lxrvalueness alias(), объявленным как 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 is T if LXR = prvalue
    • decT is T&& if LXR = xvalue
    • decT is T& if LXR = lvalue

    EXPR_DEC_TYPE возвращает decT, который совпадает с объявленным типом возврата.

  1. Если initializer является массивом, то decltype(x) или decltype((x)) не работайте просто над этим. Однако auto будет выведено на указатель.
  2. Если initializer является функцией, то применение decltype(fp) будет выведите к типу функции однако, auto выведет к его возвращаемый тип.
Таким образом, в целом auto не может рассматриваться как замена какой-либо версии decltype(), которую вы спросили.