Параметры r-значения в функции
Мне было интересно о поведении С++, когда значение r передается между функциями.
Посмотрите на этот простой код:
#include <string>
void foo(std::string&& str) {
// Accept a rvalue of str
}
void bar(std::string&& str) {
// foo(str); // Does not compile. Compiler says cannot bind lvalue into rvalue.
foo(std::move(str)); // It feels like a re-casting into a r-value?
}
int main(int argc, char *argv[]) {
bar(std::string("c++_rvalue"));
return 0;
}
Я знаю, когда я внутри функции bar
, мне нужно использовать функцию move
для вызова функции foo
. Теперь мой вопрос: почему?
Когда я внутри функции bar
, переменная str
уже должна быть r-значением, но компилятор действует так, как будто это l-значение.
Может ли кто-нибудь привести некоторую ссылку на стандарт об этом поведении?
Спасибо!
Ответы
Ответ 1
str
является ссылкой на rvalue, т.е. является ссылкой только на rvalues . Но это все еще ссылка, которая является lvalue. Вы можете использовать str
как переменную, что также означает, что это значение lvalue, а не временное rvalue.
Значение l, согласно п. 3.10.1.1:
Значение l (так называемое, исторически, поскольку lvalues может появиться в левой части выражения присваивания) обозначает функцию или объект. [Пример: Если E является выражением типа указателя, тогда * E является выражением lvalue, относящимся к объекту или функции, к которым E указывает. В качестве другого примера, результатом вызова функции, возвращаемым типом которой является lvalue reference, является lvalue. -end пример]
И значение r, согласно §3.10.1.4:
Значение r (так называемое, исторически, поскольку rvalues могут появляться в правой части задания выражение) является xvalue, временным объектом (12.2) или его подобъектом или значением, не связанным с объектом.
Исходя из этого, str
не является временным объектом и связан с объектом (с объектом с именем str
), и поэтому он не является rvalue.
В примере для lvalue используется указатель, но это то же самое для ссылок, и, естественно, для ссылок rvalue (которые являются только специальным типом ссылок).
Итак, в вашем примере str
является lvalue, поэтому вы должны std::move
его вызвать foo
(который принимает только значения rvalues, а не lvalues).
Ответ 2
"rvalue" в "rvalue reference" относится к типу значения, которое ссылка может связывать с:
- ссылки lvalue могут связываться с lvalues
- Ссылки rvalue могут связываться с rvalues
- (+ немного больше)
Это все там. Важно отметить, что это не относится к значению, которое получается при использовании ссылки. После того, как у вас есть ссылочная переменная (любая ссылка!), Идентификатор id, называющий эту переменную всегда lvalue. Rvalues встречаются в дикой природе только как временные значения, или как значения выражений вызова функции, или как значение литого выражения, или как результат распада или this
.
Здесь существует некоторая аналогия с разыменованием указателя: разыменование указателя всегда является значением lvalue, независимо от того, как был получен этот указатель: *p
, *(p + 1)
, *f()
- все lvalues. Неважно, как вы пришли к этой вещи; как только вы его получите, это значение lvalue.
Отступив немного, возможно, самым интересным аспектом всего этого является то, что ссылки rvalue являются механизмом преобразования значения rval в lvalue. Такой механизм не существовал до С++ 11, который приводил к изменчивым lvalues. Хотя преобразование lvalue-to-rvalue было частью языка с самого его начала, потребовалось гораздо больше времени, чтобы обнаружить необходимость преобразования rvalue-to-lvalue.
Ответ 3
Теперь мой вопрос: почему?
Я добавляю еще один ответ, потому что хочу подчеркнуть ответ на вопрос "почему".
Хотя названные ссылки rvalue могут связываться с rvalue, они обрабатываются как lvalues при использовании. Например:
struct A {};
void h(const A&);
void h(A&&);
void g(const A&);
void g(A&&);
void f(A&& a)
{
g(a); // calls g(const A&)
h(a); // calls h(const A&)
}
Хотя значение rvalue может привязываться к параметру a
f()
, после привязки a
теперь рассматривается как lvalue. В частности, вызовы перегруженных функций g()
и h()
разрешают перегрузки const A&
(lvalue). Лечение a
как rvalue внутри f
приведет к коду ошибки: Сначала будет выведена "версия перемещения" g()
, которая, вероятно, будет падать a
, а затем кратное a
будет отправлено на перегрузку перемещения h()
.
Ссылка.