Зачем мне нужно использовать std:: move в списке инициализации конструктора move?
Скажем, у меня есть (тривиальный) класс, который является конструктивным по переносу и переносимым, но не копируемым или подлежащим копированию:
class movable
{
public:
explicit movable(int) {}
movable(movable&&) {}
movable& operator=(movable&&) { return *this; }
movable(const movable&) = delete;
movable& operator=(const movable&) = delete;
};
Это отлично работает:
movable m1(movable(17));
Это, конечно, не работает, потому что m1
не является rvalue:
movable m2(m1);
Но я могу обернуть m1
в std::move
, который переводит его в ссылку rvalue-reference, чтобы заставить его работать:
movable m2(std::move(m1));
До сих пор так хорошо. Теперь, допустим, у меня есть (одинаково тривиальный) контейнерный класс, который содержит одно значение:
template <typename T>
class container
{
public:
explicit container(T&& value) : value_(value) {}
private:
T value_;
};
Это, однако, не работает:
container<movable> c(movable(17));
Компилятор (я пробовал clang 4.0 и g++ 4.7.2) жалуется, что я пытаюсь использовать movable
deleted copy-constructor в container
списке инициализации. Опять же, обертывание value
в std::move
заставляет его работать:
explicit container(T&& value) : value_(std::move(value)) {}
Но зачем нужен std::move
в этом случае? Не value
уже типа movable&&
? Как value_(value)
отличается от movable m1(movable(42))
?
Ответы
Ответ 1
Это потому, что value
- именованная переменная и, следовательно, lvalue. std::move
требуется вернуть его обратно в r-значение, так что оно вызовет перегрузку конструктора-конструктора T
для соответствия.
Сказать это по-другому: ссылка rvalue может привязываться к rvalue, но сама по себе не является rvalue. Это просто ссылка, и в выражении это значение. Единственный способ создать из него выражение, которое является rvalue, - это кастинг.
Ответ 2
Как value_(value)
отличается от movable m1(movable(42))
?
Именованная ссылка rvalue - это lvalue (и, таким образом, привязывается к удаленной копии ctor), а временная - это, скорее, rvalue (специальное значение должно быть специфицировано).
§5 [expr] p6
[...] В общем, эффект этого правила заключается в том, что именованные ссылки rvalue рассматриваются как lvalues, а неназванные ссылки rvalue на объекты рассматриваются как xvalues [...]
Как и в примере:
A&& ar = static_cast<A&&>(a);
Выражение ar
является lvalue.
Вышеуказанные цитаты взяты из ненормативных заметок, но являются адекватным объяснением, так как остальная часть статьи 5 идет и объясняет, какие выражения только создают xvalues † (иначе говоря, только указанные выражения и ни одна else создаст xvalues). См. Также здесь для исчерпывающего списка.
† x значениями являются одна подгруппа значений r, причем prvalues являются другой подгруппой. См. этот вопрос для объяснения.