Есть
auto x = initializer;
эквивалентно
decltype(initializer) x = initializer;
или
decltype((initializer)) x = initializer;
или нет?
Есть
auto x = initializer;
эквивалентно
decltype(initializer) x = initializer;
или
decltype((initializer)) x = initializer;
или нет?
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);
}
Wikipedia далее объясняет семантику decltype
следующим образом:
Аналогично оператору sizeof операнд decltype не оценивается. Неофициально тип, возвращаемый 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 &.
Это не сработает (и уродливо):
decltype([]() { foo(); }) f = []() { foo(); };
тогда
auto f = []() { foo(); };
будет.
Это зависит. auto
и decltype
служат для разных целей, поэтому они не сопоставляются друг с другом.
Правила для auto
проще всего объяснить, потому что они такие же, как и для вывода параметров шаблона. Я не буду расширять их здесь, но обратите внимание, что auto&
и auto&&
также являются некоторыми возможными применениями!
decltype
однако имеет несколько случаев, некоторые из которых вы проиллюстрировали выше (информация и цитаты, взятые из n3290, 7.1.6.2 Спецификаторы простого типа [dcl.type.simple]), которые я разделяю на две категории:
Неформально, я бы сказал, что decltype
может работать с любыми именами (для первого случая) или выражениями. (Формально и в соответствии с грамматикой decltype
работает на выражениях, поэтому рассмотрим первый случай как уточнение, а второй случай - как catch-all.)
При использовании имени с 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
В выражениях С++ не может быть ссылочный тип (refint()
имеет тип int
not int&
).
Обратите внимание, что lvalyeness выражения не является проблемой для выражения справа (справа от знака равенства или чего-то, что копируется вообще). Rvalue 1
обрабатывается как lvalues i1
и refint()
.
Для параметров значений (то есть не ссылочных параметров) применяется не только преобразование lvalue в rvalue, но и преобразование массива в указатель. const игнорируется.
decltype
decltype
- очень полезная функция с ужасным интерфейсом:
decltype
действует по-разному на некоторые выражения, определенные в терминах поиска имени и других выражений! Это тип функций, которые заставляют людей ненавидеть С++.
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
существует, если if 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
реализует определенную функцию (но разработчики С++ не потратили другого ключевые слова на нем.)
decltype(expr)
даст тип выражения, украшенного его lxrvalueness (lvalue/xvalue/prvalue-ness):
T
дает T
T
дает T&&
T
дает T&
Это ответное правило вызова функции: if f
- это функция с возвращаемым типом
T&
, выражение f()
является lvalueT&&
, выражение f()
представляет собой значение xvalueT
, выражение f()
является prvalue а также для бросков: для голого типа (чистый тип объекта, без ссылки) T
(T&)expr
- это lvalue(T&&)expr
- это значение xvalue(T)expr
- это prvalueСсылка - это кодирование lxrvalueness на типы. decltype
выполняет ли это кодирование, чтобы сохранить и передать значение вещей.
Это полезно, когда вы хотите использовать псевдоним выражения: 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
выражения, которое является обратным типом голым (без
ссылка)
lxrvalueness LXR выражения в соответствии с референциальностью объявленного типа возврата: lvalue для ссылки, xvalue r-reference...
затем он украшает тип T
LXR, чтобы получить тип decT
:
decT
T
, если LXR = prvaluedecT
T&&
, если LXR = xvaluedecT
T&
, если LXR = lvalue EXPR_DEC_TYPE
возвращает decT
, который совпадает с объявленным типом возвращаемого значения.
initializer
- массив, то decltype(x)
или decltype((x))
не работайте просто на нем. Однако auto
будет выведено
указатель.initializer
- это функция, то применение decltype(fp)
будет
вывести на тип функции, однако, auto
выведет его
возвращаемый тип.Таким образом, вообще говоря, auto
нельзя рассматривать как замену любой версии decltype()
, которую вы задали.