Ответ 1
Нелегко понять эти понятия, не получив формального. Праймер, вероятно, не хочет путать вас и избегает введения таких терминов, как "lvalue", "rvalue" и "xvalue". К сожалению, это важно для понимания того, как работает decltype
.
Прежде всего, тип оцениваемого выражения никогда не является ссылочным типом, а не тип const
-qualified верхнего уровня для типов неклассов (например, int const
или int&
). Если тип выражения оказывается int&
или int const
, он немедленно преобразуется в int
до любой последующей оценки.
Это указано в параграфах 5/5 и 5/6 Стандарта С++ 11:
5 Если выражение первоначально имеет тип "ссылка на Т" (8.3.2, 8.5.3), тип устанавливается до
T
до любой дальнейший анализ. Выражение обозначает объект или функцию, обозначенные ссылкой, и выражение - это lvalue или xvalue, в зависимости от выражения.6 Если изначальное значение prvalue имеет тип "cv T", где
T
- это неквалифицированный не-класс, не-массив типа cv, тип выражение доводится доT
до любого дальнейшего анализа.
Так много для выражений. Что делает decltype
? Ну, правила, определяющие результат decltype(e)
для данного выражения e
, указаны в пункте 7.1.6.2/4:
Тип, обозначенный
decltype(e)
, определяется следующим образом:- если
e
- это несферированное id-выражение или unparenthesized доступ к члену класса (5.2.5),decltype(e)
это тип объекта с именемe
. Если такой объект отсутствует или еслиe
называет набор перегруженных функций, программа плохо сформирована;- в противном случае, если
e
является значением x,decltype(e)
являетсяT&&
, гдеT
является типомe
;- в противном случае, если
e
является lvalue,decltype(e)
являетсяT&
, гдеT
является типомe
;- в противном случае
decltype(e)
является типомe
.Операндом спецификатора
decltype
является неоцениваемый операнд (пункт 5).
Это действительно может показаться запутанным. Попробуем проанализировать его по частям. Прежде всего:
- если
e
- это несферированное id-выражение или unparenthesized доступ к члену класса (5.2.5),decltype(e)
это тип объекта с именемe
. Если такой объект отсутствует или еслиe
называет набор перегруженных функций, программа плохо сформирована;
Это просто. Если e
- это просто имя переменной, и вы не помещаете ее в круглые скобки, то результатом decltype
является тип этой переменной. Так
bool b; // decltype(b) = bool
int x; // decltype(x) = int
int& y = x; // decltype(y) = int&
int const& z = y; // decltype(z) = int const&
int const t = 42; // decltype(t) = int const
Обратите внимание, что результат decltype(e)
здесь не обязательно совпадает с типом оцененного выражения e
. Например, оценка выражения z
дает значение типа int const
, а не int const&
(потому что в параграфе 5/5 &
удаляется, как мы видели ранее).
Посмотрим, что произойдет, когда выражение не просто идентификатор:
- в противном случае, если
e
является значением x,decltype(e)
являетсяT&&
, гдеT
является типомe
;
Это усложняется. Что такое xvalue? В принципе, это одна из трех категорий, выражение может принадлежать (xvalue, lvalue или prvalue). Обычно значение x получается при вызове функции с типом возвращаемого значения, который является ссылочным типом rvalue, или в результате статического приведения в тип ссылочного значения rvalue. Типичным примером является вызов std::move()
.
Чтобы использовать формулировку стандарта:
[Примечание: выражение представляет собой значение x, если оно:
- результат вызова функции, неявно или явно, чей тип возврата является ссылкой rvalue к типу объекта,
- ссылка на ссылку rvalue для типа объекта,
- выражение доступа к члену класса, обозначающее нестатический элемент данных не ссылочного типа, в котором выражение объекта является значением xvalue или
- a
.*
выражение "указатель-к-член", в котором первым операндом является значение x, а второй операнд указатель на элемент данных.В общем, эффект этого правила заключается в том, что названные ссылки rvalue обрабатываются как lvalues и unnamed rvalue ссылки на объекты рассматриваются как xvalues; rvalue ссылки на функции рассматриваются как lvalues ли или нет. -end note]
Так, например, выражения std::move(x)
, static_cast<int&&>(x)
и std::move(p).first
(для объекта p
типа pair
) являются значениями x. Когда вы применяете decltype
к выражению xvalue, decltype
добавляет &&
к типу выражения:
int x; // decltype(std::move(x)) = int&&
// decltype(static_cast<int&&>(x)) = int&&
Продолжить:
- в противном случае, если
e
является lvalue,decltype(e)
являетсяT&
, гдеT
является типомe
;
Что такое lvalue? Ну, неофициально, выражение lvalue - это выражения, которые обозначают объекты, которые могут повторяться в вашей программе, - например, переменные с именем и/или объектами, на которые вы можете взять адрес.
Для выражения e
типа T
, являющегося выражением lvalue, decltype(e)
дает T&
. Так, например:
int x; // decltype(x) = int (as we have seen)
// decltype((x)) = int& - here the expression is parenthesized, so the
// first bullet does not apply and decltype appends & to the type of
// the expression (x), which is int
Вызов функции для функции, тип возврата которой T&
также является выражением lvalue, поэтому:
int& foo() { return x; } // decltype(foo()) = int&
Наконец:
- в противном случае
decltype(e)
является типомe
.
Если выражение не является значением xvalue или lvalue (другими словами, если оно является prvalue), результат decltype(e)
является просто типом e
. Безвизовые временные ряды и литералы являются prvalues. Так, например:
int foo() { return x; } // Function calls for functions that do not return
// a reference type are prvalue expressions
// decltype(foo()) = int
// decltype(42) = int
Позвольте применить вышеприведенные примеры к вашему вопросу. Учитывая эти объявления:
int i = 3, *ptr = &i, &ref = i;
decltype(ref + 0) j;
decltype(*ptr) k;
decltype(a = b) l;
Тип j
будет int
, потому что operator +
возвращает значение типа int
. Тип k
будет int&
, так как унарный operator *
дает значение l (см. Пункт 5.3.1/1). Тип l
также int&
, потому что результат operator =
является lvalue (см. Пункт 5.17/1).
Относительно этой части вашего вопроса:
Но, переходя по второму правилу, поскольку выражение дает тип объекта, который может стоять в левой части присваивания (в этом случае int), не следует, чтобы decltype выдавал ref для int (int & ) типа?
Вероятно, вы неверно истолковали этот отрывок из книги. Не все объекты типа int
могут находиться в левой части задания. Например, назначение ниже является незаконным:
int foo() { return 42; }
foo() = 24; // ERROR! foo() is a prvalue expression, cannot be on the left
// side of an assignment
Может ли выражение появляться в левой части задания (обратите внимание, что мы говорим о встроенном операторе присваивания для основных типов данных здесь) зависит от категории значений этого выражения (lvalue, xvalue, или prvalue), а категория значений выражения не зависит от его типа.