Ответ 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).