Это было бы бессмысленно. Вы изменили бы вещь в функции, и изменение было бы немедленно потеряно, потому что вещь была фактически временной.
Причина нового типа проистекает из необходимости уметь решать, что на самом деле является значением, а что нет. Только тогда вы сможете использовать их для классных вещей, которые они используют.
string toupper(string && s) { // for nonconst rvalues
for(char &c : s) make_uppercase(c);
return move(s); // move s into a returned string object
}
string toupper(string const& s) { // for the rest
// calls the rvalue reference version, by passing
// an rvalue copy.
return toupper(string(s));
}
Теперь, если у вас есть какое-то значение rvalue и вы передаете его в toupper, значение r может быть напрямую изменено, потому что мы знаем, что временное значение в любом случае выбрасывается, поэтому мы можем просто изменить его и не нужно копировать Это. Кроме того, то же самое наблюдение используется для вещи, называемой move-constructors и move-assignment. Правая сторона не скопирована, но ее вещи просто украдены и перемещены в *this
.
Если бы вы сказали, что rvalue может связываться с неконстантными ссылками lvalue, то у вас не было бы способа выяснить, ссылается ли это на lvalue (именованный объект) или rvalue (временное) в конце.
Это, вероятно, более малоизвестно, но в любом случае полезно, вы можете поместить lvalue или rvalue ref-определители в функцию-член. Вот пример, который естественным образом расширяет существующую семантику ссылок rvalue на неявный параметр объекта:
struct string {
string& operator=(string const& other) & { /* ... */ }
};
Теперь вы не можете больше говорить
string() = "hello";
Что сбивает с толку и в большинстве случаев не имеет смысла. То, что делает &
выше, говорит о том, что оператор присваивания может быть вызван только для lvalue. То же самое можно сделать для rvalues, поставив &&
.