R-значения, l-значения и формальные определения - PullRequest
12 голосов
/ 22 июня 2019

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

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 Ответ

9 голосов
/ 22 июня 2019

Сначала несколько предварительных абзацев:

[основной]

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

[dcl.type.simple]

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

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

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

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

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

[dcl.ref]

1 В объявлении T D, где D имеет одну из форм

& <i>attribute-specifier-seq</i><sub>opt</sub> D1 
&& <i>attribute-specifier-seq</i><sub>opt</sub> 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» относится к категории значений.

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

...