Я думаю, что есть кое-что, что я не совсем понимаю в ссылках rvalue. Почему следующее не удается скомпилировать (VS2012) с ошибкой 'foo' : cannot convert parameter 1 from 'int' to 'int &&'
?
void foo(int &&) {}
void bar(int &&x) { foo(x); };
Я бы предположил, что тип int &&
будет сохранен при переходе из бара в фу. Почему это превращается в int
один раз внутри тела функции?
Я знаю, что ответ заключается в использовании std::forward
:
void bar(int &&x) { foo(std::forward<int>(x)); }
так что, может быть, я просто не понимаю Зачем. (Кроме того, почему бы и нет std::move
?)
Я всегда помню lvalue как значение, которое имеет имя или может быть адресовано. Поскольку x имеет имя, оно передается как lvalue. Цель ссылки на rvalue состоит в том, чтобы позволить функции полностью обрезать значение любым удобным для нее способом. Если мы передадим 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
используется для идеальной пересылки в шаблонах.
Почему это превращается в
int
один раз внутри тела функции?
Это не так; это все еще ссылка на Rvalue.
Когда имя появляется в выражении, это именующий — даже если это будет ссылка на Rvalue. Это может быть преобразовано в Rvalue если выражение требует этого (т. е. если необходимо его значение); но это не может быть связано с Rvalue ссылка.
Итак, как вы говорите, для того, чтобы связать его с другим Rvalue ссылка, вы должны явно преобразовать его в неназванный Rvalue. std::forward
а также std::move
удобные способы сделать это.
Кроме того, почему бы и нет
std::move
?
Почему нет? Это будет иметь больше смысла, чем std::forward
, который предназначен для шаблонов, которые не знают, является ли аргумент ссылкой.
Это «правило без имени«. Внутри bar
, x
имеет имя … x
, Так что теперь это lvalue. Передача чего-либо в функцию в качестве ссылки на rvalue не делает это значением внутри функции.
Если вы не понимаете, почему так должно быть, спросите себя — что x
после foo
возвращается? (Помните, foo
может свободно двигаться x
.)
Rvalue а также именующий категории выражения.
Rvalue ссылка а также lvalue ссылка категории Рекомендации.
Внутри декларации, T x&& = <initializer expression>
переменная x как тип T&&и это может быть связано с выражением (), которое является Rvalue выражение. Таким образом, Т&& был назван тип ссылки rvalue, потому что это относится к Rvalue выражение.
Внутри декларации, T x& = <initializer expression>
переменная x как тип T&и он может быть связан с выражением (), которое является выражением lvalue (++). Таким образом, Т& был назван Тип ссылки lvalue, потому что это может относиться к lvalue выражение.
Тогда в C ++ важно провести различие между именованием сущности, которая появляется внутри объявления, и когда это имя появляется внутри выражения.
Когда имя появляется внутри выражения, как в foo(x)
, имя x
только выражение, называемое id-выражением. По определению, а id-выражение всегда lvalue выражение и выражения lvalue не может быть привязан к Rvalue ссылка.