Связь между auto и decltype - PullRequest
34 голосов
/ 29 июля 2011

*

auto x = initializer;

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

decltype(initializer) x = initializer;

или

decltype((initializer)) x = initializer;

или ни одному?

Ответы [ 5 ]

37 голосов
/ 29 июля 2011

decltype также учитывает, является ли выражение rvalue или lvalue.

Википедия говорит ,

Тип, обозначаемый decltype, может отличаться от типа, определяемого auto.

#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, операнд типа decl не оценивается. Неформально тип, возвращаемый decltype (e), выводится следующим образом:

  • Если выражение e относится к переменной в локальной области или области имен, статической переменной-члену или параметру функции, то результатом будет объявленный тип этой переменной или параметра
  • Если e - это вызов функции или перегруженный вызов оператора, decltype (e) обозначает объявленный тип возврата этой функции
  • В противном случае, если e - lvalue, decltype (e) - это T &, где T - тип e; если e является r-значением, результатом будет 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, ни выражением доступа к члену и, следовательно, не обозначает именованный объект. Поскольку выражение является lvalue, его выводимый тип является «ссылкой на тип выражения» или const double &.

9 голосов
/ 29 июля 2011

Это не сработает (и ужасно):

decltype([]() { foo(); }) f = []() { foo(); };

тогда

auto f = []() { foo(); };

будет.

6 голосов
/ 29 июля 2011

Это зависит.auto и decltype служат разным целям, поэтому они не отображаются однозначно.

Правила для auto проще всего объяснить, поскольку они такие же, как и для вывода параметров шаблона.Я не буду здесь подробно останавливаться на них, но учтите, что auto& и auto&& также являются некоторыми возможными применениями!

decltype однако имеет несколько случаев, некоторые из которых вы иллюстрировали выше (информация и цитатывзято из n3290, 7.1.6.2. Спецификаторы простых типов [dcl.type.simple]), которые я разделяю на две категории:

  • при использовании того, что в стандарте называется «не выраженным в скобках выражением id или не заключенным в скобки классомдоступ к элементу "
  • остальное!

Неформально я бы сказал, что 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;
2 голосов
/ 15 августа 2015

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&).

Обратите внимание, что lvalueness ofВыражение не является проблемой для выражения справа (справа от знака равенства или чего-то, что копируется в целом).Rvalue 1 обрабатывается как lvalue i1 и refint().

Для параметров по значению (то есть, не ссылочных параметров) применяется не только преобразование lvalue в rvalue, но и массив дляпреобразование указателя.const игнорируется.

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 существует тогда 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 (чистое 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

Reference-ness - это кодирование lxrvalueness в типы.decltype выполняет эту кодировку для сохранения и передачи lxrvalueness вещей.

Это полезно, когда вы хотите создать псевдоним выражения: lxrvalueness выражения expr, которое является вызовом функции (либо нормально * 1120)* или с синтаксисом оператора, таким как 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 выражения в соответствии со ссылкамизаявленный тип возврата: lvalue для справки, xvalue r-reference ...

    , затем он украшает тип T с помощью LXR , чтобы получить тип decT:

    • decT равно T, если LXR = prvalue
    • decT равно T&&, если LXR = xvalue
    • decT равно T&, если LXR = lvalue

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

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

Таким образом, в целом auto нельзя рассматривать как замену любой версии decltype(), которую вы просили.

...