Параметры 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().

Ссылка.