Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a CopyConstructible type also have to be MoveConstructible?

As stated in cppreference, a requirement for a type T to be CopyConstructible is that it also be MoveConstructible.

The draft for the STL CopyConstructible concept contains:

template <class T>
concept CopyConstructible =
   std::MoveConstructible<T> &&
   std::Constructible<T, T&> && std::ConvertibleTo<T&, T> &&
   std::Constructible<T, const T&> && std::ConvertibleTo<const T&, T> &&
   std::Constructible<T, const T> && std::ConvertibleTo<const T, T>;

which supports the named requirement statement. Given the above definition, a type like:

struct HaveCopy {
   HaveCopy(const HaveCopy&)  = default;
   HaveCopy(HaveCopy&&)       = delete;
   HaveCopy& operator= (const HaveCopy&)  = default;
   HaveCopy& operator= (HaveCopy&&)       = delete;
};

fails the simple test:

static_assert(std::CopyConstructible<HaveCopy>);

whereas it passes the old:

static_assert(std::is_copy_constructible<HaveCopy>::value);

So, the question is why? What's the intention of the standards committee on that matter? HaveCopy is not move constructible but is pretty much copy constructible in my opinion and std::is_copy_constructible<> agrees with me.

The same behavior is also inherited by the Copyable concept, which is:

template <class T>
concept Copyable =
   std::CopyConstructible<T> &&
   std::Movable<T> &&
   std::Assignable<T&, const T&>;

So the test:

static_assert(std::Copyable<HaveCopy>);

will fail also. This time the failure is doubled. Both CopyConstrucible<> and Movable<> disagree that HaveCopy is copyable.

The discussion here is somehow similar but does not answer the why. Why do we need this behavior? Does this kind of check exclude valid copy constructible types or is HaveCopy not copy constructible at all? The last seems really weird to me if it is true.

Any thoughts?

like image 203
hoo2 Avatar asked Feb 06 '19 11:02

hoo2


1 Answers

Yes, CopyConstructible the concept is quite different than the type trait std::is_copy_constructible. You're focusing on the move constructor, but there are many other cases to consider as well. Do you think this type should be CopyConstructible?

struct A {
    A(A&) = delete;
    A(A const&);
};

How about this one?

struct B {
    explicit B(B const&);
};

The point is, there's an endless variety of combinations of constructors you can write. That doesn't mean that they're all meaningful or worth supporting. Having a type with a copy constructor but a deleted move constructor simply does not make sense.

Concepts isn't about just doing a syntactic check, it's also about enforcing semantic requirements to push for meaningful types - which end up being easier to code around. If you simply check is_copy_constructible, all you're allowing yourself to do is to explicitly construct your type from a const lvalue. Writing T x = y;, even if y is a const T, is already outside of that scope! That might be literally what copy constructible means, but it's a lot less meaningful than the broader "I can construct a T from a T" - which is a lot closer to really what we think of when we consider copying. That's what the concept CopyConstructible gives us.

As you go through the library, there are other concepts that require more (syntactically and semantically) than a direct translation of their name might suggest. EqualityComparableWith<T,U> doesn't just check that I can write t == u but also u == t, t != u, and u != t as well. StrictTotallyOrdered doesn't just check the ordering operators, it also checks ==. Having a cohesive whole is important.

like image 84
Barry Avatar answered Oct 11 '22 08:10

Barry