Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::copyable subsume std::movable?

According to cppreference, std::copyable is defined as follows:

template <class T>
concept copyable =
  std::copy_constructible<T> &&
  std::movable<T> && // <-- !!
  std::assignable_from<T&, T&> &&
  std::assignable_from<T&, const T&> &&
  std::assignable_from<T&, const T>;

I'm wondering why a copyable object should be also movable. Just think about a global variable that is accessed by several functions. While it makes sense to copy that variable (for example to save its state before calling another function) it makes no sense, and actually would be very bad, to move it since other functions might not know that that variable is currently in an unspecified state. So why exactly does std::copyable subsume std::movable ?

like image 437
user7769147 Avatar asked Jul 09 '20 08:07

user7769147


Video Answer


2 Answers

This comes from two facts. Firstly, even if you don't define move constructor + move assignment you can still construct/assign object from r-value reference if you define copying functions. Just take a look at the example:

#include <utility>

struct foo {
    foo() = default;
    foo(const foo&) = default;
    foo& operator=(const foo&) = default;
};

int main()
{
    foo f;
    foo b = std::move(f);
}

Secondly (and maybe more importantly), the copyable type can always be (or according to standard now must be) also movable in some way. If object is copyable then worst case scenario for move is just copying internal data.

Note that since I declared copy constructor the compiler DID NOT generate default move constructor.

like image 166
bartop Avatar answered Nov 18 '22 12:11

bartop


While it makes sense to copy that variable (for example to save its state before calling another function) it makes no sense, and actually would be very bad, to move it since other functions might not know that that variable is currently in an unspecified state.

There's a strong, unstated presumption here of what moving actually means that is probably the source of confusion. Consider the type:

class Person {
    std::string name;
public:
    Person(std::string);
    Person(Person const& rhs) : name(rhs.name) { }
    Person& operator=(Person const& rhs) { name = rhs.name; return *this; }
};

What does moving a Person do? Well, an rvalue of type Person can bind to Person const&... and that'd be the only candidate... so moving would invoke the copy constructor. Moving does a copy! This isn't a rare occurrence either - moving doesn't have to be destructive or more efficient than copying, it just can be.

Broadly speaking, there are four sane categories of types:

  1. Types for which move and copy do the same thing (e.g. int)
  2. Types for which move can be an optimization of copy that consumes resources (e.g. string or vector<int>)
  3. Types which can be moved but not copied (e.g. unique_ptr<int>)
  4. Types which can be neither moved nor copied (e.g. mutex)

There are a lot of types that fall into group 1 there. And the kind of variable mentioned in OP should also fall into group 1.

Notably missing from this list is a type that is copyable but not movable, since that makes very little sense from an operational stand-point. If you can copy the type, and you don't want destructive behavior on moving, just make moving also copy the type.

As such, you can view these groups as a kind of hierarchy. (3) expands on (4), and (1) and (2) expand on (3) - you can't really differentiate (1) from (2) syntactically. Hence, copyable subsumes movable.

like image 42
Barry Avatar answered Nov 18 '22 11:11

Barry