Зачем мне использовать std::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, чтобы заставить его работать:

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 в списке инициализации container. Опять же, обертывание value в std::move заставляет его работать:

    explicit container(T&& value) : value_(std::move(value)) {}

Но зачем в этом случае std::move? Разве value уже не относится к типу movable&&? Чем value_(value) отличается от movable m1(movable(42))?


person sarnesjo    schedule 30.10.2012    source источник
comment
Мы надеемся, что за этим последует ссылка на стандарты, но не все случаи использования переменной && должны быть деструктивными — вы можете представить себе актера, который выполняет дюжину шагов со своим аргументом &&, прежде чем уничтожить его. Поэтому, когда вы создаете другой объект с помощью переменной &&, вам все равно нужно сказать, что это конкретное использование может быть перемещением...   -  person Yakk - Adam Nevraumont    schedule 30.10.2012
comment
Именованное значение типа T&& является lvalue, тогда как безымянное значение того же типа является rvalue.   -  person avakar    schedule 30.10.2012
comment
На моем телефоне так не ответишь, но именованный временный - это lvalue. Здесь value — выражение lvalue.   -  person Joseph Mansfield    schedule 30.10.2012
comment
@Yakk: ссылки на стандарты не требуются. Это подходящий ответ.   -  person Benjamin Lindley    schedule 30.10.2012


Ответы (2)


Это потому, что value является именованной переменной и, следовательно, lvalue. std::move требуется, чтобы привести его обратно к rvalue, чтобы он привел к совпадению перегрузки конструктора перемещения T.

Другими словами: ссылка на rvalue может связываться с rvalue, но сама по себе rvalue не является. Это просто ссылка, а в выражении это lvalue. Единственный способ создать из него выражение, которое является значением r, — это приведение.

person Kerrek SB    schedule 30.10.2012

Чем value_(value) отличается от movable m1(movable(42))?

Именованная ссылка на rvalue — это lvalue (и, таким образом, она будет привязана к удаленному копирующему ctor), а временная — это, ну, rvalue (точнее, prvalue).

§5 [expr] p6

[...] В общем, эффект этого правила заключается в том, что именованные ссылки rvalue обрабатываются как lvalue, а неименованные ссылки rvalue на объекты обрабатываются как значения x [...]

А также из примера:

A&& ar = static_cast<A&&>(a);

Выражение ar является lvalue.

Приведенные выше цитаты взяты из ненормативных примечаний, но являются адекватным объяснением, поскольку остальная часть пункта 5 идет и объясняет, какие выражения только создают xvalues (то есть только указанные выражения, и никто другой не создаст значения x). Также см. здесь исчерпывающий список.

† xvalues ​​— это одна подгруппа rvalue, а prvalue — другая подгруппа. Смотрите этот вопрос для объяснения.

person Xeo    schedule 30.10.2012