Вначале говорили про присваивание/не присваивание, или можно взять адрес или нет.
В одну стоону это неверно в таком примере:
const int x = 5;
x = 4;
В другую сторону: когда делаешь vb[5]
получается временный bit-reference
которому присваивается значение (почти как string() = ...;
)
Первое заблуждение про мув-семантику: rvalue и lvalue - свойство объектов. Это не так, потому что это почти синтаксическое свойство - потому что это к выражению относится. Можно взять конкретную строчку кода и спросить это rvalue или lvalue, объект может участвовать в выражениях обоих типов, и поэтому это не свойство объекта.
Expression:
- lvalue
1.1. identifier - последовательность символов, имя переменной
1.2. =,op= присваивание стандартных типов
1.3. prefix ++ --
1.4. unary * разыменовывание
1.5. ?: (тернарный оператор) - если оба выражения lvalue
1.6. comma - если правый операнд lvalue
1.7. function call if return value is lvalue-reference(f)
1.8. cast-expression same rool for 1.7 - rvalue
2.1. literal - последовательность символов
2.2. +,-,* etc результаты операторов
2.3. postfix ++ --
2.4. unary & + -
2.5. ?: если хоть одно rvalue (потмоу что на момент компиляции)
2.6. commma - если правый операнд rvalue
2.7. function call if return value is not reference or is rvalue-ref(&&)
2.8. cast-expression same rool for 2.7
int a = 1;
int& b = a;
int& c = 2; // CE
lvalue-reference нельзя инициализировать с помощью rvalue выражения (если не константная ссылка, иначе там продление работает) При этом rvalue-reference можно инициализировать только rvalue-значениями.
Можно писать так:
int&& e = 5;
int& g = e;
Просто потому что e
- это identifier, поэтому lvalue по определению.
У константных ссылок работают обычные правила с константностью, единственное отличие - const lvalue-reference можно биндить к rvalue.
void f(int& x);
только lvalue
void f(int&& x);
только rvalue
Будет перегрузка от lvalue/rvalue. А как сделать чтобы можно было по ссылке принимать оба вида value?
template <typename T>
void g(T&& x);
Такая функция будет принимать как rvalue так и lvalue.
Если шаблонная функция имеет типом параметра T&& где T - тип ее шаблонного аргумента, то такая функция может принимать как rvalue и lvalue. Синтаксис как выше в примере. При этом должно быть ровно как там (с шаблном, без конста и других типов)
type(expr) = int&&, expr - rvalue => T = int, type(x) = int&&
Но что если так?
type(expr) = int&, expr - lvalue => T = int&, type(x) = int&
Неочевидно, потому что type(x) = int&&&, и но & + && = &
Арифметика такая:
& + & = &
& + && = &
&& + & = &
&& + && = &&
type(expr) = int, expr = lvalue, T = int&, type(x) = int&
Компилятор специально добавляет & к Т, чтобы оп арифметике получить type(x) = int&
А что с функцией std::move? Мы хотим чтобы он трактовался как rvalue. Он собственно говоря так и делает - трактует lvalue как rvalue.
tempalte <typename T>
std::remove_Reference_t<T>&& move(T&& x) noexcept {
return static_cast<std::remove_reference_t<T>&&>(x);
}
Такое странное возвращаемое значение, чтобы в любом случае вернуть T&&, потому что если бы x был int&, то по арифметике убралось &&. Поэтому мы убираем все &, и навешиваем &&