Ответ 1
Начнем с функции перемещения (которую я немного почистил):
template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
return static_cast<typename remove_reference<T>::type&&>(arg);
}
Давайте начнем с более простой части - то есть, когда функция вызывается с rvalue:
Object a = std::move(Object());
// Object() is temporary, which is prvalue
и наш шаблон move
получает экземпляр следующим образом:
// move with [T = Object]:
remove_reference<Object>::type&& move(Object&& arg)
{
return static_cast<remove_reference<Object>::type&&>(arg);
}
Так как remove_reference
преобразует T&
в T
или T&&
в T
, а Object
не является ссылкой, наша конечная функция:
Object&& move(Object&& arg)
{
return static_cast<Object&&>(arg);
}
Теперь вы можете подумать: нам даже нужен актерский состав? Ответ: да, мы делаем. Причина проста; указанная ссылка rvalue рассматривается как lvalue (и неявное преобразование от lvalue к rvalue-ссылке запрещено стандартом).
Вот что происходит, когда мы вызываем move
с lvalue:
Object a; // a is lvalue
Object b = std::move(a);
и соответствующий move
экземпляр:
// move with [T = Object&]
remove_reference<Object&>::type&& move(Object& && arg)
{
return static_cast<remove_reference<Object&>::type&&>(arg);
}
Опять же, remove_reference
преобразует Object&
в Object
и мы получаем:
Object&& move(Object& && arg)
{
return static_cast<Object&&>(arg);
}
Теперь мы перейдем к сложной части: что означает Object& &&
и как оно может связываться с lvalue?
Чтобы обеспечить идеальную пересылку, стандарт С++ 11 предоставляет специальные правила для свертывания ссылок, которые заключаются в следующем:
Object & & = Object &
Object & && = Object &
Object && & = Object &
Object && && = Object &&
Как вы можете видеть, в соответствии с этими правилами Object& &&
на самом деле означает Object&
, который является простой ссылкой lvalue, которая позволяет связывать lvalues.
Конечная функция:
Object&& move(Object& arg)
{
return static_cast<Object&&>(arg);
}
который не похож на предыдущий экземпляр с rvalue - они оба передают свой аргумент в rvalue reference, а затем возвращают его. Разница в том, что первое экземплярирование можно использовать только с rvalues, а второе - с lvalues.
Чтобы объяснить, зачем нам нужно remove_reference
немного больше, попробуйте эту функцию
template <typename T>
T&& wanna_be_move(T&& arg)
{
return static_cast<T&&>(arg);
}
и создайте его с помощью lvalue.
// wanna_be_move [with T = Object&]
Object& && wanna_be_move(Object& && arg)
{
return static_cast<Object& &&>(arg);
}
Применяя указанные выше правила сбрасывания ссылок, вы можете увидеть, что мы получаем функцию, которая непригодна для использования в качестве move
(просто, вы называете ее lvalue, вы получаете lvalue назад). Во всяком случае, эта функция является функцией тождества.
Object& wanna_be_move(Object& arg)
{
return static_cast<Object&>(arg);
}