Понимание ссылок rvalue
Я думаю, что есть что-то, что я не совсем понимаю о ссылках на rvalue. Почему следующие команды не скомпилируются (VS2012) с ошибкой 'foo' : cannot convert parameter 1 from 'int' to 'int &&'
?
void foo(int &&) {}
void bar(int &&x) { foo(x); };
Я бы предположил, что тип int &&
будет сохранен при передаче из bar в foo. Почему он преобразуется в int
один раз внутри тела функции?
Я знаю, что ответ заключается в использовании std::forward
:
void bar(int &&x) { foo(std::forward<int>(x)); }
так что, возможно, я просто не понимаю, почему. (Кроме того, почему бы не std::move
?)
Ответы
Ответ 1
Я всегда помню lvalue как значение, которое имеет имя или может быть адресовано. Поскольку x имеет имя, оно передается как lvalue. Цель ссылки на rvalue - разрешить функции полностью clobber-значение любым способом, который он считает нужным. Если мы передадим x по ссылке, как в вашем примере, тогда у нас нет способа узнать, безопасно ли это сделать:
void foo(int &&) {}
void bar(int &&x) {
foo(x);
x.DoSomething(); // what could x be?
};
Выполнение foo(std::move(x));
явно сообщает компилятору, что вы закончили с x и больше не нуждаетесь в нем. Без этого движения с существующим кодом могут случиться плохие вещи. std::move
является защитой.
std::forward
используется для идеальной пересылки в шаблонах.
Ответ 2
Почему он преобразуется в int
один раз внутри тела функции?
Это не так; это все еще ссылка на rvalue.
Когда в выражении появляется имя, это значение lvalue - даже если оно является ссылкой на rvalue. Он может быть преобразован в rvalue, если выражение требует (то есть, если его значение необходимо); но он не может быть привязан к ссылке rvalue.
Итак, как вы говорите, чтобы привязать его к другой ссылке rvalue, вы должны явно преобразовать ее в неназванное rvalue. std::forward
и std::move
- это удобные способы сделать это.
Кроме того, почему бы не std::move
?
Почему не так? Это будет иметь больше смысла, чем std::forward
, который предназначен для шаблонов, которые не знают, является ли аргумент ссылкой.
Ответ 3
Это правило no name. Внутри bar
, x
имеет имя... x
. Так что теперь это lvalue. Передача чего-либо функции в качестве ссылки rvalue не делает ее значением внутри функции.
Если вы не видите, почему так должно быть, спросите себя: что после foo
возвращается x
? (Помните, foo
может свободно перемещаться x
.)
Ответ 4
rvalue и lvalue являются категориями выражений.
Ссылка rvalue и ссылка lvalue являются категориями ссылок.
Внутри объявления, T x&& = <initializer expression>
, переменная x имеет тип T && & amp;, и она может быть связана с выражением (the), которое является выражением rvalue. Таким образом, T & был назван ссылочным типом rvalue, потому что он ссылается на выражение rvalue.
Внутри объявления T x& = <initializer expression>
переменная x имеет тип T & и ее можно связать с выражением(), которое является выражением lvalue (++). Таким образом, T & был назван ссылочным типом lvalue, потому что он может ссылаться на выражение lvalue.
Затем в C++ важно провести различие между именованием сущности, которая появляется внутри объявления, и тем, когда это имя появляется внутри выражения.
Когда имя появляется внутри выражения, как в foo(x)
, само имя x
является выражением, называемым id-выражением. По определению, id-выражение всегда является выражением lvalue, и выражения lvalue не могут быть связаны со ссылкой на rvalue.