У меня возникли некоторые трудности с интерпретацией пункта 5.2.1.1 в параграфе §8.5.3/5 N4140.


Фрагмент ниже компилирует

#include <iostream>
int& f() { static int i = 100; std::cout << i << 'n'; return i; }

int main()
{
    int& r = f();
    r = 101;
    f();
}

И вывести значения (живой пример)

100
101
Теперь, читая §8.5.3/5 в N4140, я вижу, что он компилируется из-за точки маркера (5.1.1), то есть ссылка является ссылкой lvalue, выражение инициализатора является lvalue и int совместимо с int (или с int& - я не знаю точно, какой из них я должен использовать здесь).

Пуля очков (5.1) и (5.1.1):

- Если ссылка - это ссылка lvalue и выражение инициализатора

 — is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with
   “cv2 T2,” or ...
Теперь предположим, что я изменяю левую ссылку на значение в объявлении int& r = f(); на правую ссылку на значение, т. е. int&& r = f();. Я знаю, что код не будет компилироваться, так как ссылка rvalue не привязывается к lvalue. Но мне любопытно, как прийти к такому выводу, используя стандарт?

Я объясню, в чем мои трудности:

  1. очевидно, что int&& r = f(); покрыто точкой маркера (5.2), потому что ссылка - это ссылка rvalue.

Пункт маркера (5.2):

- В противном случае ссылка должна быть ссылкой lvalue на a энергонезависимый тип const (то есть cv1 должен быть const), или ссылка будет ссылкой rvalue.

  1. в принципе, я бы сказал, что (5.2.1.1) поддерживает эту инициализацию, поскольку инициализатор является функцией lvalue и int является ссылочной совместимой с int (или с int&).

Пункты маркера (5.2.1) и (5.2.1.1):

- Если выражение инициализатора

— is an xvalue (but not a bit-field), class prvalue, array prvalue or function
  lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or ...

Edit

Я включил дословно маркированные точки из N4140 (C++14), которые эквивалентны аналогичным маркированным точкам в N3337 (C++11).

1 3

1 ответ:

Выражение инициализатора является lvalue и int является ссылочно-совместимым с int (или с int& - я не знаю точно, какой из них я должен использовать здесь).

Ссылочная совместимость-это отношение, применяемое к указанному типу, а не к ссылочному типу. Например, [dcl.в этом.ref] / 5 говорит об интиализации " ссылки на тип cv1 T1 выражением типа cv2 T2", и позже сравнивает, например, " где T1 не имеет отношения к ссылке T2".

Тип выражения f() является просто int, несмотря на то, что возвращаемый тип f является int&. Выражения просто не имеют ссылочного типа, когда мы их наблюдаем(*); ссылка отсекается и используется для определения категории ценности (см. [expr]/5). Для int& f() выражение f() является значением lvalue; для int g() выражение g() является значением rvalue.

(*)чтобы быть совершенно точным, выражения могут иметь ссылочный тип в Стандартный , но только в качестве" начального " результирующего типа. Ссылка отбрасывается "до любого дальнейшего анализа", что подразумевает, что эта ссылочность просто не наблюдаема через тип.


Теперь предположим, что я изменяю левую ссылку на значение в объявлении int& r = f(); на правую ссылку на значение, т. е. int&& r = f();. Я знаю, что код не будет компилироваться, так как ссылка rvalue не привязывается к lvalue. Но мне любопытно, как прийти к этому выводу, используя Стандарт?
Путаница, как видно из обсуждения в комментариях, по-видимому, заключается в том, что f() не является функцией lvalue. Категории ценностей, как "значение" и "значение rvalue" свойства выражений. Поэтому термин " функция lvalue "должен относиться к выражению, а именно к выражению типа функции с категорией значений"lvalue" .

Но выражение f() является выражение вызова функции. Грамматически, это postfix-выражение , постфиксом которого является список аргументов функции. Согласно [expr.вызов] / 10:

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

И [expr.вызов]/3

Если постфиксное выражение обозначает деструктор [...]; в противном случае тип функции выражение вызова-это возвращаемый тип статически выбранной функции [...]

То есть (наблюдаемый см. выше) тип выражения f() является int, а категория значения - "lvalue". Обратите внимание, что (наблюдаемый) тип не int&.

Значение функции lvalue - это, например, id-выражение , подобное f, результат косвенного указания указателя функции или выражение, дающее любой вид ссылки на функция:

using ft = void();
void f();
ft&  l();
ft&& r();
ft*  p();

// function lvalue expressions:
f
l()
r()
*p()

[expr.подтянутый.general] / 8 указывает, что такие идентификаторы, как f, являются, как id-выражениями, lvalues:

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


Вернемся к примеру int&& r = f();. Используя какой-то проект после N4296.

[dcl.в этом.ref]

5 ссылка на тип " cv1 T1" инициализируется выражением типа "cv2 T2" следующим образом:

  • (5.1) если ссылка является ссылкой lvalue и выражением инициализатора

Ссылка является ссылкой rvalue. 5.1 не применяется.

  • (5.2) в противном случае ссылка является ссылкой lvalue на a энергонезависимый тип const (например, cv1 должен быть const), или ссылка будет ссылкой rvalue. [пример опущен]

Это применимо, ссылка является rvalue-ссылкой.

  • (5.2.1) если выражение инициализатора
    • (5.2.1.1)-это xvalue (но не битовое поле), класс prvalue, массив prvalue или функция lvalue и [...], или
    • (5.2.1.2) имеет тип класса (т. е. T2 является типом класса) [...]

Инициализатор-это lvalue типа int. 5.2.1 не применяется.

  • (5.2.2) в противном случае:
    • (5.2.2.1) если T1 или T2 является типом класса [...]
    • (5.2.2.2) в противном случае-временный тип " cv1 T1" создается и инициализируется копированием (dcl.init) из выражения инициализатора. Ссылка затем привязывается к временному.

Наконец, применяется пункт 5.2.2.2. Однако:

Если T1 относится к T2:

  • (5.2.2.3) почтовый индекс CV1 должен быть таким же резюме-квалификация, или больше резюме квалификация, чем, cv2; и
  • (5.2.2.4) если ссылка является ссылкой rvalue, выражение инициализатора не должно быть значением lvalue.

T1 и T2 являются int (the ссылка возвращаемого типа f() удаляется и используется только для определения категории значений), поэтому они связаны с ссылками. cv1 и cv2 оба пусты. ссылка является ссылкой rvalue, а f() - lvalue, следовательно, 5.2.2.4 делает программу плохо сформированной.


Причина, по которой термин "значение функции lvalue" появляется в 5.2.1.1, может быть связана с проблемой "значений функции rvalue" (см., например, N3010 - ссылки на Rvalue как " забавные" Lvalues ). В C++03 не было значений функций rvalues, и, похоже, комитет не хотел вводить их в C++11. Без ссылок на rvalue, я думаю, что невозможно получить функцию rvalue. Например, вы не можете привести к типу функции и не можете возвращать типы функций из функции.

Вероятно, для согласованности значения функций lvalue могут быть привязаны к ссылкам rvalue на типы функций через приведение:

template<typename T>
void move_and_do(T& t)
{
    T&& r = static_cast<T&&>(t); // as if moved
}

int i = 42;
move_and_do(i);

move_and_do(f);

Но для T, являющегося функцией типа void(), категория значения static_cast<T&&>(t) является lvalue (отсутствуют значения типа функции). Следовательно, ссылки rvalue на типы функций могут быть привязаны к значениям функций lvalue.