Какое стандартное предложение обязывает это преобразование lvalue в rvalue?

Дано:

int main() {
   int x = 0;
   int y = x; // <---
}

Может кто-нибудь сказать, пожалуйста, какой пункт стандарта (предпочтение 2003 года) обязывает преобразование выражения x от lvalue до rvalue в инициализации объекта y?

(Или, если я ошибаюсь и такого преобразования не происходит, я бы тоже хотел это узнать!)

5 ответов

Решение

Я считаю, что проще (если не точнее на 100%) думать о lvalue-s как о реальных объектах и ​​rvalue-s как о значении, хранящемся в объекте. Выражение x является выражением lvalue, которое относится к объекту x определяется в первой строке, но при использовании в качестве правой части присвоения типу, который не является определяемым пользователем типом, читается фактическое значение, и именно здесь выполняется преобразование из lvalue в rvalue: чтение содержимого предмет.

Что касается конкретного предложения в стандарте, которое диктует это преобразование... ну, самое близкое, что я могу подумать, это 4.1 [conv.lvalue]/2 (преобразование Lvalue в Rvalue):

Значение, содержащееся в объекте, указанном lvalue, является результатом rvalue.

Требование, чтобы правая часть присваивания была r-значением, неявно или отсутствует в 5.17 [expr.ass], но это так, иначе следующее выражение будет ошибкой, так как rhs является r-значением и нет Преобразование rvalue в lvalue:

int x = 5;

РЕДАКТИРОВАТЬ: Для инициализации, 8.5 [dcl.init]/14, последний маркер (который относится к фундаментальным типам) состояния (выделено мое):

  • В противном случае начальное значение инициализируемого объекта является (возможно, преобразованным) значением выражения инициализатора. [...]

Это значение означает, что выражение lvalue в вашем примере читается (т.е. преобразуется в rvalue). Во всяком случае, здесь можно применить предыдущий абзац, который ссылается на присваивание: если для инициализации требуется lvalue, а не rvalue, выражение int i = 0; будет плохо сформирован.

Я действительно считаю, что это в некоторой степени интуитивно понятно (что уже говорили другие - значение необходимо, поэтому существует очевидная необходимость преобразования указателя объекта в значение, содержащееся в нем). Лучшее, что я мог придумать, к 4р3:

Выражение e может быть неявно преобразовано в тип T тогда и только тогда, когда объявление "T t=e;" является корректным для некоторой изобретенной временной переменной t (8.5). Эффект неявного преобразования аналогичен выполнению объявления и инициализации, а затем использованию временной переменной в качестве результата преобразования. Результатом является lvalue, если T является ссылочным типом (8.3.2), и rvalue в противном случае. Выражение e используется как lvalue, если и только если инициализация использует его как lvalue.

Обратите внимание на "если и только если" в конце - инициализатор для этого используется как значение r, потому что инициализация использует его как значение r (результат преобразования). Так что к 3.10p7

Всякий раз, когда lvalue появляется в контексте, где ожидается rvalue, lvalue преобразуется в rvalue; см. 4.1, 4.2 и 4.3.


РЕДАКТИРОВАТЬ: абзац для ввода 4p3 можно найти в 8.5p16, последний пункт:

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

Также обратите внимание на комментарии ниже.

Инициализатор имеет следующую грамматику:

initializer:
        = initializer-clause
        ( expression-list )

initializer-clause:
    assignment-expression
    { initializer-list ,opt }
    { }

В вашем примере x является assignment-expression которая следует за этой цепочкой грамматических производств:

conditional-expression  ==>
    logical-or-expression ==>
        logical-and-expression  ==>
            inclusive-or-expression  ==>
                exclusive-or-expression  ==>
                    and-expression  ==>
                        equality-expression  ==>
                            relational-expression  ==>
                                shift-expression  ==>
                                    additive-expression  ==>
                                        multiplicative-expression  ==>
                                            pm-expression  ==>
                                                cast-expression  ==>
                                                    unary-expression  ==>
                                                        postfix-expression  ==>
                                                            primary-expression  ==> 
                                                                id-expression  ==>
                                                                    unqualified-id  ==>
                                                                        identifier

И идентификатор "является lvalue, если сущность является функцией или переменной" (5.1/4 "Первичные выражения").

Так что в вашем примере выражение справа от = это выражение, которое оказывается lvalue, Это может быть rvalue конечно, но это не должно быть. И нет обязательного преобразования lvalue в rvalue.

Я не уверен, какова ценность в знании этого, хотя.

Это то, что вы ищете:

§3.10 / 7

Всякий раз, когда lvalue появляется в контексте, где ожидается rvalue, lvalue преобразуется в rvalue; см. 4.1, 4.2 и 4.3.

И я думаю, когда ты пишешь int y = x, это в основном копирует значение, содержащееся в объекте x которое является lvalue, но само значение является rvalue, следовательно, контекст ожидает rvalue.

§4.1 / 2 говорит,

Значение, содержащееся в объекте, указанном lvalue, является результатом rvalue.

Возможно, эти две цитаты проясняют ваши сомнения. Поправь меня, если мое понимание неверно. Я хотел бы узнать новые вещи.


@ Комментарий Томалака:

Моя проблема в том, что int& y = x; допустимо, поэтому в этом случае, конечно, x не может быть r-значением. Я не знаю, насколько неважна разница в моем примере, хотя

Что ж int &y = x НЕ копирует значение. Он просто создает псевдоним самого объекта. Но, как я уже сказал ранее int y = x, в основном копирует значение, являющееся значением. Следовательно, контекст ожидает значение r, поскольку здесь выполняется копирование.

3.10 Lvalues ​​и rvalues

1 Каждое выражение является либо lvalue, либо rvalue.

2 Значение относится к объекту или функции. Некоторые выражения rvalue - выражения класса или класса cvqualified - также ссылаются на объекты.47)

3 [Примечание: некоторые встроенные операторы и вызовы функций приводят к lvalues. [Пример: если E является выражением типа указателя, тогда *E является выражением lvalue, ссылающимся на объект или функцию, на которые указывает E. В качестве другого примера, функция int& f(); возвращает lvalue, поэтому вызов f () является выражением lvalue. ]

  1. [Примечание: некоторые встроенные операторы ожидают lvalue операндов. [Пример: все встроенные операторы присваивания ожидают, что их левые операнды будут lvalue. ] Другие встроенные операторы выдают значения r, а некоторые ожидают их. [Пример: унарные и бинарные операторы + ожидают значения rvalue и дают результаты rvalue. ] Обсуждение каждого встроенного оператора в разделе 5 указывает, ожидает ли он операндов lvalue и дает ли он значение lvalue. ]

5 Результатом вызова функции, которая не возвращает ссылку, является значение r. Определяемые пользователем операторы являются функциями, и то, ожидают ли такие операторы или выдают lvalues, определяется их параметрами и типами возвращаемых данных.

6 Выражение, которое содержит временный объект, полученный в результате приведения к типу без ссылки, является значением r (это включает явное создание объекта с использованием функциональной нотации (5.2.3)).

7 Всякий раз, когда lvalue появляется в контексте, где ожидается rvalue, lvalue преобразуется в rvalue; см. 4.1, 4.2 и 4.3.

8 Обсуждение исходной инициализации в 8.5.3 и временных значений в 12.2 указывает на поведение значений l и значений в других значимых контекстах.

9 Значения класса могут иметь cvqualified типы; неклассовые значения всегда имеют cvunqualified типы. R-значения всегда должны иметь полные типы или тип void; в дополнение к этим типам, lvalues ​​также может иметь неполные типы.

10 lvalue для объекта необходимо для того, чтобы изменить объект, за исключением того, что rvalue типа class может также использоваться для изменения его референта при определенных обстоятельствах. [Пример: функция-член, вызываемая для объекта (9.3), может модифицировать объект. ]

11 Функции не могут быть изменены, но указатели на функции могут быть изменены.

12 Указатель на неполный тип может быть изменяемым. В какой-то момент в программе, когда указанный тип завершен, объект, на который указывает указатель, также может быть изменен.

13 Референт выражения, подвергнутого предварительному определению, не должен изменяться (посредством этого выражения), за исключением того, что если он относится к типу класса и имеет изменяемый компонент, этот компонент может быть изменен (7.1.5.1).

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

15 Если программа пытается получить доступ к сохраненному значению объекта через значение lvalue, отличное от одного из следующих типов, поведение не определено48): - динамический тип объекта, - квалифицированная версия динамического типа объекта, - тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта, - тип, который является типом со знаком или без знака, соответствующим cvqualified версии динамического типа объекта, - тип агрегата или объединения, который включает в себя один из вышеупомянутых типов среди его членов (включая, рекурсивно, член субагрегата или автономного объединения), - тип, который является (возможно, cvqualified) типом базового класса динамического типа объекта, - char или unsigned char тип.

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