Simon
Simon

Reputation: 603

Are custom preconditions valid for a move assignment operator?

Is it valid to place custom preconditions on the state of the move target in a move assignment operator? In particular, would it be valid to only allow moving to an object that has not been fully initialized before?

Consider:

struct Foo {
private:
  std::unique_ptr<int> value;

public:
  Foo(std::unique_ptr<int> value) : value{std::move(value)} {}

  Foo(Foo&&) noexcept = default;
  Foo &operator =(Foo&& other) noexcept {
    assert(!value);
    value = std::move(other.value);
    return *this;
  }
};

I wonder, e.g., if this class can be used with a container, without hitting the assertion. E.g. if you do:

    std::vector<Foo> foo;
    foo.emplace_back(std::make_unique<int>(42));
    foo.emplace_back(std::make_unique<int>(17));
    Foo removed = std::move(foo[0]);
    foo.erase(foo.begin());

Were this guaranteed to work with the class, or is this relying on implementation details of std::vector?

Upvotes: 2

Views: 113

Answers (1)

Howard Hinnant
Howard Hinnant

Reputation: 218780

Is it valid to place custom preconditions on the state of the move target in a move assignment operator?

Yes, but...

In particular, would it be valid to only allow moving to an object that has not been fully initialized before?

Sure, but...

Consider:

No problem so far, but...

std::vector<Foo> foo;
// ...

You can do anything you want with your class until you use it with std-code (or somebody else's code) which puts requirements on your type.

For example the standard says this about vector::erase:

For vector and deque, T is Cpp17MoveAssignable.

And Cpp17MoveAssignable is defined here.

t = rv

rv’s state is unspecified. [Note: rv must still meet the requirements of the library component that is using it, whether or not t and rv refer to the same object. The operations listed in those requirements must work as specified whether rv has been moved from or not. —end note]

Foo doesn't fully meet the Cpp17MoveAssignable requirements. And that's fine as long as you don't expect Foo to work with code that requires Cpp17MoveAssignable.

Disclaimer: A future standard might relax the requirements on vector::erase so as to allow Foo. But that is not the case today.

Note that std::remove_if also requires Cpp17MoveAssignable:

http://eel.is/c++draft/alg.remove#2

And this slight modification of your program actually will assert:

#include <algorithm>
#include <cassert>
#include <memory>
#include <vector>

struct Foo {
private:
  std::unique_ptr<int> value;

public:
  Foo(std::unique_ptr<int> value) : value{std::move(value)} {}

  Foo(Foo&&) noexcept = default;
  Foo &operator =(Foo&& other) noexcept {
    assert(!value);
    value = std::move(other.value);
    return *this;
  }

  bool operator==(int i) const {return *value == i;}
};

int
main()
{
    std::vector<Foo> foo;
    foo.push_back(std::make_unique<int>(1));
    foo.push_back(std::make_unique<int>(2));
    foo.push_back(std::make_unique<int>(3));
    std::remove_if(foo.begin(), foo.end(),
        [](auto const& f) {return f == 1;});
}

Upvotes: 1

Related Questions