Как отличить rvalue от lvalue?

Рейтинг: 0Ответов: 1Опубликовано: 13.03.2023

У меня есть некоторый класс A. Мне необходимо сделать функцию func(A a), которая принимает в качестве аргумента lvalue от класса А, но не принимает rvalue от класса A. В случае попытки использовать rvalue класса A - компилятор должен указать на ошибку. Вот пример программы, которая на мой взгляд не должна работать, но компилятор не указывает никакой ошибки. Каким образом - в результате какого правила или какой логики rvalue превращается в lvalue и наделяется адресом? Если это делает какой-то конструктор копирования, можно ли его переопределить? Я пробовал запускать эту программу на Visual Studio 2012, 2019 с опциями Treat Specific Warnings As Error 4238;4239, а также на gcc, но они даже не сообщают о том, что при вызове func, rvalue превращается в lvalue.

class A
{   const int value;
public:
    A(const int from_any_type) :value(from_any_type)
    {
    }
};

const A A_rvalue(const A from_any_type)
{   const A a = from_any_type;
    return a; //return as rvalue
}

void func(const A& from_lvalue)
{  //here we get lvalue
    const A* real_address = &from_lvalue;
}

int main()
{
    func(A_rvalue(3));
    return 0;
}

Ответы

▲ 3

Неправильно думать, что у rvalue нет адреса. Компилятор просто не дает его взять, чтобы вам было сложнее отстрелить себе ногу. (Я сейчас не говорю про prvalue, это отдельная мутная тема.)

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

Например, если у вас есть A a;, то выражение a - это lvalue, а выражение std::move(a) - это rvalue, хотя они оба обозначают один и тот же объект (у которого, конечно, есть адрес).


Ваш код работает потому, что константные lvalue ссылки в виде исключения разрешено инициализировать rvalue.

Можно либо использовать неконстантную ссылку: (но тогда константные lvalue тоже не будут работать)

void func(A &from_lvalue) {...}

Либо явно запретить вызов на rvalue вот так:

void func(const A &from_lvalue) {...}
void func(const A &&) = delete;

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

То же самое с переменной, которую вы отдаете в return. Обычно компилятор автоматически делает там перемещение, но с const переменной так не получится.