Во-первых, ваши функции n1
и n2
возвращают битые ссылки, потому что объекты, на которые они указывают, умирают при выходе из функции.
Поэтому тот вариант присваивания, который компилируется, все равно вызывает неопределенное поведение.
Из функций получается всегда возвращаются rvalue значения, тк без имени
Неверно. Правило работает наоборот: любое имя - lvalue. Но бывают lvalue и без имен.
n1()
возвращает lvalue.
n()
и n2()
возвращают rvalue. (Eсли точнее, они возвращают prvalue и xvalue соотвественно - это два подвида rvalue. Но здесь между ними похоже нет никакой разницы.)
Как в этом убедиться? Вот так.
#include <iostream>
template <typename T>
void PrintType()
{
std::cout <<
#ifdef _MSC_VER
__FUNCSIG__
#else
__PRETTY_FUNCTION__
#endif
<< '\n';
}
PrintType<decltype((n()))>(); // void PrintType() [T = int]
PrintType<decltype((n1()))>(); // void PrintType() [T = int &]
PrintType<decltype((n2()))>(); // void PrintType() [T = int &&]
А дальше считаете амперсанды в выводе.
= prvalue, &
= lvalue, &&
= xvalue.
Обратите внимание на двойные скобки после decltype
. Здесь они не влияют на ответ, но в других случаях могут влиять. (Например, int &&x = 42;
, decltype(x)
= int &&
, но decltype((x)) = int &
. Здесь x
, как ни странно, lvalue, ведь у него есть имя.)
Из вашего примера можно сделать вывод, что rvalue нельзя ничего присваивать, а lvalue можно.
Но в общем случае это не так. Это верно только для скалярных типов (т.е. целых и дробных чисел, enum-ов, указателей, и т.п.). А классам можно присваивать всегда (если только создатель класса это явно не запретил).
Вывод - такой запрет - это искусственное ограничение, защита от выстрела себе в ногу. У lvalue, как и у rvalue, есть адреса. То, что у rvalue нельзя его взять напрямую - это тоже защита от дурака.
(Тут еще нужно объяснение про то, что у prvalue строго говоря адресов нет, и это вообще не объекты - но это занудство, и тут не влияет на результат. Можете почитать про mandatory copy elision.)
int&& v = 8;
У меня ощущение, что тут был немой вопрос - "как здесь получилось присвоить rvalue?".
Ответ - это вообще не присваивание, это инициализация.
Присваиванием было бы int &&v = 8; v = 42;
. А вот это компилируется, потому что v
- lvalue (выше объяснил почему). Если превратить его в rvalue через std::move
, то перестанет компилироваться: std::move(v) = 42;
.