R-значения, l-значения и формальные определения

Люди смущаются, когда слышат это в

int&& x

x имеет ссылочный тип rvalue, но x является lvalue. Непонимание связано с тем, что идентификаторы и выражения - это разные вещи, а также типы и категории значений. Кроме того, типы выражений "корректируются до дальнейшего анализа", и слова "rvalue" и "lvalue" могут появляться как в имени типа, так и в имени категории значения.

Я хочу уточнить формальные определения. Предположим, у нас есть функция:

1 | void f(int&& x) {           
2 |     ... = x;               
3 |     ... = std::move(x);
4 | }

Правильны ли следующие утверждения?

  1. В строке 1 x является идентификатором (id-выражением), который называет параметр функции. Его тип - int&&, и это тип, который decltype(x). x не является выражением и не имеет категории значения.
  2. В строке 2 x является выражением. Перед настройкой типа его тип является int&&, а после типа становится int. Категория значения - lvalue.
  3. В строке 3 std::move(x) является выражением. Его тип до настройки - int&&, после - int. Категория значения - xvalue.
  4. Когда мы говорим, что x имеет ссылочный тип rvalue, мы ссылаемся либо на тип x в качестве идентификатора, либо на тип x в качестве выражения перед настройкой типа.
  5. Слово "тип" в выражении "Каждое выражение имеет некоторый нереферентный тип, и каждое выражение принадлежит ровно к одной из трех основных категорий значений" на cppreference.com относится к типу после корректировки типа.
  6. Когда Скотт Мейерс пишет: "Если тип выражения является ссылкой на lvalue (например, T& или const T& и т.д.), То это выражение является lvalue". он относится к типу перед корректировкой, а второе слово "lvalue" относится к категории значений.

Ответы

Ответ 1

Некоторые предварительные параграфы сначала:

[Основная]

3 Сущность - это значение, объект, ссылка, функция, перечислитель, тип, член класса, шаблон, специализация шаблона, пространство имен, пакет параметров или эта.

[dcl.type.simple]

4 Тип, обозначаемый decltype(e), определяется следующим образом:

  • если e - это не выраженное в скобках id-выражение или не заключенное в скобки обращение к члену класса ([expr.ref]), decltype(e) - это тип сущности, названный e. Если такой сущности нет, или если e называет набор перегруженных функций, программа некорректна;

  • в противном случае, если e - значение x, decltype(e) - это T&&, где T - тип e;

  • в противном случае, если e - это decltype(e), decltype(e) - это T&, где T - это тип e;

  • в противном случае decltype(e) является типом e.

[dcl.ref]

1 В декларации TD где D имеет одну из форм

& attribute-specifier-seqopt D1 
&& attribute-specifier-seqopt D1

и тип идентификатора в объявлении T D1 является "производным-декларатором-типом-списком T ", тогда тип идентификатора D является "производной-декларатором-тип-ссылкой на список T ".

[выражение]

5 Если выражение изначально имеет тип "ссылка на T " ([dcl.ref], [dcl.init.ref]), тип корректируется до T перед любым дальнейшим анализом. Выражение обозначает объект или функцию, обозначенную ссылкой, и выражение является lvalue или xvalue, в зависимости от выражения.

[expr.prim.general]

8 Идентификатор - это id-выражение при условии, что оно было надлежащим образом объявлено (пункт [dcl.dcl]). Тип выражения - это тип идентификатора. Результатом является объект, обозначенный идентификатором. Результатом является lvalue, если объект является функцией, переменной или элементом данных, и prvalue в противном случае.

[expr.call]

10 Вызов функции - это lvalue, если тип результата является ссылочным типом lvalue или ссылкой rvalue на тип функции, xvalue, если тип результата является ссылкой rvalue на тип объекта, и prvalue в противном случае.

Который сейчас позволяет нам отвечать на ваши вопросы.

В строке 1 x является идентификатором (id-выражением), который называет параметр функции. Его тип - int&&, и это тип, который decltype(x). x не является выражением и не имеет категории значения.

Да вроде. x в объявлении не является выражением. Но в качестве аргумента для decltype используется выражение. Однако он попадает в особый случай первого decltype поэтому выводится тип идентификатора с именем x, а не тип x в качестве выражения.

В строке 2 x является выражением. Перед настройкой типа его тип является int&&, а после типа становится int. Категория значения - lvalue.

Да.

В строке 3 std::move(x) является выражением. Его тип до настройки - int&&, после - int. Категория значения - xvalue.

Да.

Когда мы говорим, что x имеет ссылочный тип rvalue, мы ссылаемся либо на тип x в качестве идентификатора, либо на тип x в качестве выражения перед настройкой типа.

Да.

Слово "тип" в выражении "Каждое выражение имеет некоторый нереферентный тип, и каждое выражение принадлежит ровно к одной из трех основных категорий значений" на cppreference.com относится к типу после корректировки типа.

Да.

Когда Скотт Мейерс пишет: "Если тип выражения является ссылкой на lvalue (например, T& или const T& и т.д.), То это выражение является lvalue". он относится к типу перед корректировкой, а второе слово "lvalue" относится к категории значений.

Не могу точно сказать, что имел в виду Скотт Мейерс, когда писал это, но это единственная интерпретация слов, которая соответствует стандарту, да.