lvalue для значения неявного преобразования

Я вижу термин «преобразование lvalue-to-rvalue», используемый во многих местах по всему стандарту C ++. Насколько я могу судить, такое преобразование часто делается неявным образом.

Одной из неожиданных (для меня) особенностей формулировки из стандарта является то, что они решают рассматривать значение lvalue-to-rvalue как преобразование. Что, если бы они сказали, что значение glvalue всегда приемлемо, а не prvalue. Будет ли эта фраза иметь другой смысл? Например, мы читаем, что lvalues ​​и xvalues ​​являются примерами glvalues. Мы не читаем, что lvalues ​​и xvalues ​​преобразуются в glvalues. Есть ли разница в значении?

Прежде чем я впервые столкнулся с этой терминологией, я более или менее моделировал lvalues ​​и rvalues ​​следующим образом: «lvalues всегда могут действовать как rvalues, но, кроме того, могут появляться на левой стороне a = и справа от & .

Для меня это интуитивное поведение, что, если у меня есть имя переменной, я могу разместить это имя везде, где я бы поставил литерал. Эта модель, похоже, согласуется с терминологией неявных преобразований lvalue-to-rval, используемой в стандарте, до тех пор, пока это неявное преобразование гарантировано произойдет.

Но, поскольку они используют эту терминологию, я начал задаваться вопросом, может ли неявное преобразование lvalue-rvalue в некоторых случаях не произойти. То есть, возможно, моя ментальная модель здесь не так. Вот соответствующая часть стандарта: (спасибо комментаторам).

Всякий раз, когда glvalue появляется в контексте, где ожидается prvalue, glvalue преобразуется в prvalue; см. 4.1, 4.2 и 4.3. [Примечание: попытка привязки ссылки rvalue к lvalue не является таким контекстом; см. 8.5.3.)

Я понимаю, что они отмечают в примечании:

 int x = 1; int && y = x; //in this declaration context, x won't bind to y. // but the literal 1 would have bound, so this is one context where the implicit // lvalue to rvalue conversion did not happen. // The expression on right is an lvalue. if it had been a prvalue, it would have bound. // Therefore, the lvalue to prvalue conversion did not happen (which is good). 

Итак, мой вопрос:

1) Может ли кто-нибудь прояснить контексты, где это преобразование может произойти неявно? В частности, кроме контекста привязки к ссылке rvalue, есть ли другие, где преобразования lvalue-rvalue не могут выполняться неявно?

2) Кроме того, в скобках [Note:...] в предложении кажется, что мы могли бы понять это из предложения раньше. Какая часть стандарта была бы такой?

3) Означает ли это, что привязка ссылки rvalue не является контекстом, где мы ожидаем выражения prvalue (справа)?

4) Как и в других преобразованиях, преобразование glvalue-to-prvalue включает работу во время выполнения, что позволит мне это наблюдать?

Моя цель здесь не в том, чтобы спросить, желательно ли разрешить такое преобразование. Я пытаюсь научиться объяснять себе поведение этого кода с использованием стандарта в качестве отправной точки.

Хороший ответ будет рассмотрен в приведенной выше цитате и объясните (на основе parsingа текста), является ли заметка в ней также неявной из ее текста. Тогда он может добавить любые другие кавычки, которые позволят мне узнать другие контексты, в которых это преобразование может не произойти неявно или объяснить, что таких контекстов больше нет. Возможно, общее обсуждение того, почему glvalue to prvalue считается конверсией.

Я думаю, что преобразование lvalue-to-rvalue больше, чем просто использование lvalue, где требуется rvalue . Он может создать копию classа и всегда дает значение , а не объект.

Я использую n3485 для «C ++ 11» и n1256 для «C99».


Объекты и ценности

Наиболее кратким описанием является C99 / 3.14:

объект

область хранения данных в среде исполнения, содержимое которой может представлять значения

Также есть немного в C ++ 11 / [intro.object] / 1

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

Таким образом, объект содержит значение (может содержать).


Категории значений

Несмотря на свое название, категории значений classифицируют выражения, а не значения. lvalue-выражения даже не могут считаться значениями.

Полную таксономию / категоризацию можно найти в [basic.lval]; вот обсуждение StackOverflow .

Вот части об объектах:

  • Lvalue ([…]) обозначает функцию или объект. […]
  • Значение xvalue (значение «eXpiring») также относится к объекту […]
  • Значение glvalue («обобщенное» lvalue) является значением l или значением x.
  • Rvalue ([…]) – это xvalue, временный объект или подобъект или значение, которое не связано с объектом.
  • Значение prvalue («чистое» rvalue) является значением r, которое не является значением x. […]

Обратите внимание на фразу «значение, не связанное с объектом». Также обратите внимание, что поскольку выражения xvalue относятся к объектам, истинные значения всегда должны встречаться как выражения prvalue.


Преобразование lvalue-to-rvalue

Как указано в сноске 53, теперь его следует называть «преобразованием с помощью glvalue-to-prvalue». Во-первых, вот цитата:

1 Значение gl нефункционного типа без массива T может быть преобразовано в prvalue. Если T является неполным типом, программа, которая требует этого преобразования, плохо сформирована. Если объект, к которому относится glvalue, не является объектом типа T и не является объектом типа, производного от T , или если объект не инициализирован, программа, которая требует этого преобразования, имеет неопределенное поведение. Если T является неclassовым типом, то тип prvalue является cv-неквалифицированной версией T В противном случае тип prvalue равен T

В этом первом абзаце указаны требования и результирующий тип преобразования. Он еще не связан с эффектами преобразования (кроме неопределенного поведения).

2 Когда преобразование lvalue-rvalue происходит в неоцененном операнде или его подвыражении, значение, содержащееся в ссылочном объекте, не получает доступа. В противном случае, если glvalue имеет тип classа, копия преобразования – инициализирует временный тип T из glvalue, а результат преобразования – это значение для временного. В противном случае, если glvalue имеет (возможно, cv-qualified) тип std::nullptr_t , результат prvalue является константой нулевого указателя. В противном случае значение, содержащееся в объекте, обозначенном glvalue, является результатом prvalue.

Я бы сказал, что вы увидите преобразование lvalue-to-rvalue, наиболее часто применяемое к типам неclassов. Например,

 struct my_class { int m; }; my_class x{42}; my_class y{0}; x = y; 

Выражение x = y не применяет преобразование lvalue-to-rval к y ( my_class , это создавало бы временный my_class ). Причина в том, что x = y интерпретируется как x.operator=(y) , который принимает значение y по умолчанию по ссылке , а не по значению (для ссылки привязки, см. Ниже: он не может связывать rvalue, поскольку это будет временным объектом отличной от y ). Однако определение my_class::operator= по умолчанию применяет преобразование lvalue-to-rval в xm .

Поэтому самая важная часть для меня, похоже,

В противном случае значение, содержащееся в объекте, обозначенном glvalue, является результатом prvalue.

Как правило, преобразование lvalue-to-rvalue просто считывает значение из объекта . Это не просто преобразование no-op между категориями ценности (выражения); он может даже создать временный вызов, вызвав конструктор копирования. И преобразование lvalue-to-rvalue всегда возвращает значение prvalue, а не (временный) объект .

Обратите внимание, что преобразование lvalue-to-rvalue не является единственным преобразованием, которое преобразует lvalue в prvalue: существует также преобразование от массива к указателю и преобразование функции в указатель.


значения и выражения

Большинство выражений не дают объектов [[править]] . Однако идентификатор id может быть идентификатором , который обозначает объект . Объект является сущностью, поэтому существуют выражения, которые дают объекты:

 int x; x = 5; 

Левая часть выражения-назначения x = 5 также должна быть выражением. x здесь является id-выражением , так как x является идентификатором. Результатом этого id-выражения является объект, обозначенный x .

Выражения применяют неявные преобразования: [expr] / 9

Всякий раз, когда выражение glvalue появляется как операнд оператора, ожидающего prvalue для этого операнда, стандартные преобразования преобразования lvalue-to-rvalue, array-to-pointer или function-to-pointer применяются для преобразования выражения в prvalue.

И / 10 о обычных арифметических преобразованиях, а также / 3 о пользовательских преобразованиях.

Мне бы хотелось теперь процитировать оператора, который «ожидает prvalue для этого операнда», но не может найти ничего, кроме приведения. Например, [expr.dynamic.cast] / 2 «Если T – тип указателя, v [операнд] должен быть значением знака указателя на полный тип classа».

Обычные арифметические преобразования, требуемые многими арифметическими операторами, вызываются преобразованием lvalue-rvalue косвенно через стандартное преобразование. Все стандартные преобразования, но три, которые конвертируют из lvalues ​​в rvalues, ожидают prvalues.

Однако простое назначение не вызывает обычных арифметических преобразований. Он определен в [expr.ass] / 2 как:

В простом присваивании ( = ) значение выражения заменяет значение объекта, на которое ссылается левый операнд.

Поэтому, хотя явно не требуется выражение prvalue с правой стороны, оно требует значения . Мне не ясно, если это строго требует преобразования lvalue-to-rvalue. Существует аргумент, согласно которому доступ к значению неинициализированной переменной должен всегда вызывать неопределенное поведение (см. Также CWG 616 ), независимо от того, присваивается ли это значение объекту или добавляется его значение к другому значению. Но это неопределенное поведение требуется только для преобразования lvalue-to-rvalue (AFAIK), которое тогда должно быть единственным способом доступа к значению, хранящемуся в объекте.

Если это более концептуальное представление действительно, нам нужно преобразование lvalue-to-rvalue для доступа к значению внутри объекта, тогда было бы намного легче понять, где оно (и должно быть) применено.


инициализация

Как и при простом назначении, обсуждается , требуется ли преобразование lvalue-to-rval для инициализации другого объекта:

 int x = 42; // initializer is a non-string literal -> prvalue int y = x; // initializer is an object / lvalue 

Для основных типов [dcl.init] / 17 последний пункт указывает:

В противном случае начальным значением инициализируемого объекта является (возможно, преобразованное) значение выражения инициализатора. Стандартные конверсии будут использоваться, если необходимо, для преобразования выражения инициализатора в cv-неквалифицированную версию типа назначения; не учитываются определенные пользователем преобразования. Если преобразование не может быть выполнено, инициализация плохо сформирована.

Однако он также упомянул значение выражения инициализатора . Подобно простому присваиванию-выражению, мы можем считать это косвенным вызовом преобразования lvalue-to-rvalue.


Связывание ссылок

Если мы увидим преобразование lvalue-to-rvalue как способ доступа к значению объекта (плюс создание временного для операндов типа classа), мы понимаем, что он вообще не применяется для привязки к ссылке: Ссылка – это значение lvalue , он всегда относится к объекту. Поэтому, если мы привязываем значения к ссылкам, нам нужно создать временные объекты, содержащие эти значения. И это действительно так, если выражение-инициализатор ссылки является значением prvalue (которое является значением или временным объектом):

 int const& lr = 42; // create a temporary object, bind it to `r` int&& rv = 42; // same 

Запрет на привязку к значению lvalue запрещен, но prvalues ​​типов classов с функциями преобразования, которые дают ссылки lvalue, могут быть привязаны к lvalue-ссылкам преобразованного типа.

Полное описание ссылочного связывания в [dcl.init.ref] довольно длинное и довольно не по теме. Я думаю, что суть этого вопроса, связанного с этим вопросом, заключается в том, что ссылки ссылаются на объекты, поэтому не преобразование glvalue-to-prvalue (object-to-value).

На glvalues: glvalue («обобщенное» lvalue) является выражением, которое является либо значением l, либо значением x. Значение glval может быть неявно преобразовано в prvalue с использованием значения lvalue-to-rvalue, массива-указателя или неявного преобразования функции-в-указателя.

Преобразования Lvalue применяются, когда аргумент lvalue (например, ссылка на объект) используется в контексте, где ожидается значение rvalue (например, число).

Lvalue to rvalue conversion
Значение gl любого нефункционного типа без массива T может быть неявно преобразовано в prvalue того же типа . Если T является неclassовым типом, это преобразование также удаляет cv-квалификаторы. Если не встречается в неоцененном контексте (в операнде sizeof, typeid, noexcept или decltype), это преобразование эффективно копирует-создает временный объект типа T, используя исходное значение glvalue как аргумент конструктора, и этот временный объект возвращается как prvalue , Если glvalue имеет тип std :: nullptr_t, итоговое значение prvalue является константой null pointer nullptr.

Давайте будем гением компьютера.