Какие выражения дают ссылочный тип, когда к ним применяется метод decltype?

Я читал С++ Primer и не мог понять, когда выражение дает тип объекта, и когда он дает ссылочный тип объекту.

Я цитирую из книги:

  • Когда мы применяем decltype к выражению, которое не является переменной, мы получаем тип, который дает это выражение.
  • Вообще говоря, decltype возвращает ссылочный тип для выражений, которые дают объекты, которые могут стоять в левой части задания.

Учитывая следующий код:

int i = 3, *ptr = &i, &ref = i;
decltype(ref + 0) j;

В приведенном выше коде выражение "ref + 0" приводит к присущей операции добавления значения объекта, к которому ref ссылается, я и 0. Следовательно, при первом правиле выражение дает тип int. Но, переходя по второму правилу, поскольку выражение дает тип объекта, который может стоять в левой части присваивания (в этом случае int), не должен ли decltype присвоить номер int (int &)?

В книге также говорится, что для следующего кода

decltype(*ptr) k;

k имеет тип int & а не int, тип, к которому приводит выражение.

Он также говорит, что для выражения присваивания, как в коде ниже

decltype(a = b) l;

l будет иметь тип ссылки на объект в левой части операции присваивания.

Как мы узнаем, какие выражения дают тип объекта и которые дают ссылку на тип объекта?

Ответ 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), а категория значений выражения не зависит от его типа.

Ответ 2

Для выражений, как и в ваших примерах, decltype будет предоставлять ссылочный тип, если аргумент равен lvalue.

7.1.6.2p4:

The type denoted by decltype(e) is defined as follows:
  — if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e)     is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions,     the program is ill-formed;
  — otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
  — otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
  — otherwise, decltype(e) is the type of e.
The operand of the decltype specifier is an unevaluated operand (Clause 5).
[ Example:
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = i; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4 = x3; // type is const double&
—end example ]