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

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

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

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

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

37

Решение

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

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


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

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

объект

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

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

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

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


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

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

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

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

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

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


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

Как указывает сноска 53, теперь ее следует называть «преобразованием glvalue-to-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-to-rvalue к y (это создаст временный my_class, Кстати). Причина в том, что x = y интерпретируется как x.operator=(y), который занимает y по умолчанию по ссылке, не по значению (для привязки ссылки см. ниже; он не может связать значение r, так как это будет временный объект, отличный от y). Тем не менее, определение по умолчанию my_class::operator= действительно применяет преобразование lvalue-в-значение x.m,

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

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

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

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

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

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

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


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

Как и в случае простого назначения, есть обсуждение требуется ли преобразование 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-prvalue (объект-значение).

25

Другие решения

О 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.

2

По вопросам рекламы [email protected]