Подтвердить что ты не робот

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

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

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

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

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

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

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

Я понимаю, что они описывают в примечании, следующее:

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

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

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

4b9b3361

Ответ 1

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

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


Объекты и значения

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

объект

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

Там также бит в С++ 11/[intro.object]/1

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

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


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

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

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

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

  • Значение lvalue ([...]) обозначает функцию или объект. [...]
  • Значение xvalue (значение "eXpiring" ) также относится к объекту [...]
  • Значение glval ( "обобщенное" lvalue) является значением lvalue или значением x.
  • Значение rvalue ([...]) является xvalue, временным объектом или подобъектом или значением, не связанным с объектом.
  • Значение prvalue ( "чистое" значение r) является значением r, которое не является значением x. [...]

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


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

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

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

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

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

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

struct my_class { int m; };

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

x = y;

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

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

В противном случае значение, содержащееся в объекте, указанном 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 [операнд] должен быть значением знака указателя для завершения типа класса".

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

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

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

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

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


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

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

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

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

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

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

Ответ 2

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

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

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