Зачем мне нужно использовать std:: move в списке инициализации конструктора move? - программирование

Зачем мне нужно использовать 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))?

4b9b3361

Ответ 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 ​​являются другой подгруппой. См. этот вопрос для объяснения.