lvalue в rvalue неявное преобразование

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

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

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

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

Но, поскольку они используют эту терминологию, я начал задаваться вопросом, может ли в некоторых случаях произойти неявное преобразование из 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-в-значение не происходят неявно?

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

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

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

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

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

3 ответа

Решение

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

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


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

Самое краткое описание в C99/3.14:

объект

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

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

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

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


Ценовые категории

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

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

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

  • Lvalue ([...]) обозначает функцию или объект. [...]
  • Значение xvalue (значение "eXpiring") также относится к объекту [...]
  • Glvalue ("обобщенное" lvalue) - это lvalue или xvalue.
  • Значение rvalue ([...]) представляет собой значение xvalue, временный объект или его подобъект или значение, которое не связано с объектом.
  • Prvalue ("чистое" rvalue) - это rvalue, которое не является xvalue. [...]

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


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

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

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

Этот первый абзац определяет требования и конечный тип конверсии. Это еще не касается эффектов преобразования (кроме Неопределенного Поведения).

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

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

struct my_class { int m; };

my_class x{42};
my_class y{0};

x = y;

Выражение x = y не применяет преобразование lvalue-в-значение к y (это создаст временный my_class, Кстати). Причина в том, что x = y интерпретируется как x.operator=(y), который занимает y по умолчанию по ссылке, а не по значению (для привязки ссылки см. ниже; он не может связать значение r, поскольку это будет временный объект, отличный от y). Тем не менее, определение по умолчанию my_class::operator= действительно применяет преобразование lvalue-в-значение x.m,

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

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

Поэтому обычно преобразование lvalue в rvalue просто читает значение из объекта. Это не просто неопределяемое преобразование между категориями значений (выражений); он может даже создать временный, вызывая конструктор копирования. И преобразование 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 о пользовательских преобразованиях.

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

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

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

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

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

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


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

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

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 в rvalue как способ доступа к значению объекта (плюс создание временного для операндов типа класса), мы понимаем, что оно обычно не применяется для привязки к ссылке: ссылка является lvalue, это всегда относится к объекту. Поэтому, если мы привязываем значения к ссылкам, нам нужно было бы создавать временные объекты, содержащие эти значения. И это действительно так, если выражение-инициализатор ссылки является prvalue (значением или временным объектом):

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

Привязка prvalue к ссылке lvalue запрещена, но prvalue типов классов с функциями преобразования, которые выдают ссылки lvalue, может быть привязан к ссылкам lvalue преобразованного типа.

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

О glvalue: glvalue ("обобщенное" lvalue) - это выражение, которое является либо lvalue, либо xvalue. Glvalue может быть неявно преобразовано в prvalue с помощью неявного преобразования lvalue-to-rvalue, array-to-pointer или function-to-pointer.

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

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

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

Таким образом, он может выйти из строя во время выполнения, если lvalue недопустимо ( ссылка, сформированная из недопустимого указателя, висячая ссылка, целевой объект которой вышел за пределы области видимости и т. д.) или результат не инициализирован.

Теперь оптимизации компилятора могут изменить порядок фактического чтения памяти, объединить несколько операций чтения путем кэширования значения в регистре ЦП и т. д. Но смысл всегда заключается в извлечении существующего значения объекта.

Вы получите неопределенное поведение, если гипотетическое чтение памяти не удастся, даже если оптимизатор преобразовал код, чтобы избежать фактического чтения памяти. Возможное переупорядочение/спекулятивное чтение являются одним из источников «неопределенного поведения при путешествии во времени», когда путь кода, который достигнет выборки из недопустимого lvalue, начинает вести себя странно раньше.

Другие вопросы по тегам