Why does std::copyable subsume std::movable?
Asked Answered
P

2

9

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 ?

Pneumatic answered 9/7, 2020 at 8:23 Comment(3)
Interesting answer to a somewhat related question https://mcmap.net/q/122388/-when-to-make-a-type-non-movable-in-c-11Suctorial
Also see this linked section from the original move semantics proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2002/…Bela
The technical capacity to move any object of a given type (even if we incorrectly limit “move” to moves that are not implemented as copies) does not imply that moving one particular object is a good idea. You can probably also assign to it; don’t do that if you shouldn’t.Submergible
S
3

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.

Spleenful answered 9/7, 2020 at 8:56 Comment(7)
Even thought rvalues can bind to const lvalues, the code you posted works because the compiler generated the move constructor implicitly. If you explicitly delete it, the code won't compile and you would get a copyable but unmovable type which makes no sense for the reason you explained. Looks like concepts don't care about every particular case as type traits do, and we've already seen that by looking at the differences between is_convertible and convertible_to, is_default_constructible and default_initializable etc...Pneumatic
Could it be related to the named requirements CopyConstructible and CopyAssignable that also requires the type to be movable? The named requirements are stricter than the corresponding type traits (is_copy_constructible and is_copy_assignable) if I'm not mistaken.Moody
@Pneumatic This is untrue, compiler does not generate move functions if there are user defined copy functions. Explicit delete would just make compiler disregard r-val ref to const l-val ref conversionSpleenful
@Spleenful in fact foo's copy constructor is not user defined, it's explicitly defaultedPneumatic
@Pneumatic I wrote a mistake, I meant user declared copy constructor blocks autogeneration of move constructor. And it is user declared.Spleenful
@user7769147: I agree with bartop here. The standard is very picky about user-declared vs user-defined. Even if a special member function isn't user-defined, it can still be user-declared (as default), which in the case of a copy ctor will disable the move ctor.Sharie
I didn't know that, I always thought that there were no differences between defaulting and letting the compiler generate the constructor for you. +1 for that other reason :)Pneumatic
P
1

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.

Pale answered 9/7, 2020 at 13:4 Comment(2)
The reasoning that "you can't move without copy [therefore copy implies move]" is a non sequitur at best. The OP was about a general (and sane, too :) ) scenario, where no moving is ever considered, at all, but copying very much is. So why moving is still being involved with that case, like a pushy relative at a wedding table, near the cake, is not adequately explained.Invocate
@Invocate I don't know what you're quoting - I said no such thing. Forbidding move but allowing copy makes no sense and is pretty user-hostile compared to simply making move itself copy.Pale

© 2022 - 2024 — McMap. All rights reserved.