Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does `std::pair<int, movable>` require a [deleted] `const&` copy constructor?

I'm busy testing an implementation of various generic algorithms and I'm using types with minimal support of provided functions. I came across this weird setup when using a std::pair<T, movable> with some type T (e.g., int) and a movable type defined like this:

struct movable
{
    movable() {}
    movable(movable&&) = default;
    // movable(movable const&) = delete;
    movable(movable&) = delete;
};

The idea is have a type which is movable but not copyable. That works great, e.g., with expressions like this:

movable m1 = movable();
movable m2 = std::move(m1);

However, when trying to use this type as a member of std::pair<...> it fails! To make get the code to compile it is necessary to add the deleted(!) copy constructor taking a movable const& (or have only that version). The copy constructor taking a non-const reference is insufficient:

#include <utility>
auto f() -> std::pair<int, movable> {
    return std::pair<int, movable>(int(), movable());
}

What is going on here? Is std::pair<...> overspecified by mandating that std::pair(std::pair const&) is = defaulted?

The problem seems to be down to the specification of std::pair's copy constructor (in 20.3.2 [pairs.pair] synopsis):

 namespace std {
     template <class T1, class T2>
     struct pair {
         ...
         pair(const pair&) = default;
         ...
     };
 }

A quick check with my implementation implies that the obvious implementation copying the two members does not require the const& version of the movable copy constructor. That is, the offensive part is the = default on pair's copy constructor!

like image 980
Dietmar Kühl Avatar asked Dec 23 '14 19:12

Dietmar Kühl


1 Answers

std::pair copy constructor is declared as follows:

pair(const pair&) = default;

By declaring this copy constructor for movable:

movable(movable&) = delete;

you inhibit implicit creation of movable(const movable&) (so it's not even deleted, there's just no such constructor), thus this is the only copy constructor you have. But std::pair copy constructor requires a copy constructor of its members to take const reference, so you get compile error.

If you add this:

movable(movable const&) = delete;

or (better) just remove movable(movable&) = delete; declaration, you now have the movable(movable const&) constructor, and because it's deleted, the std::pair copy constructor also becomes deleted.

Update: Let's consider a simpler example demonstrating the same issue. This doesn't compile:

template <typename T>
struct holder {
    T t;
    // will compile if you comment the next line
    holder(holder const&) = default;
    // adding or removing move constructor changes nothing WRT compile errors
    // holder(holder&&) = default;
};

struct movable {
    movable() {}
    movable(movable&&) = default;
    // will also compile if you uncomment the next line
    //movable(movable const&) = delete;
    movable(movable&) = delete;
};

holder<movable> h{movable()};

It will compile if you comment the copy constructor of holder, because this is how implicit copy constructor generation works ([class.copy]/8:

The implicitly-declared copy constructor for a class X will have the form

X::X(const X&)

if each potentially constructed subobject of a class type M (or array thereof) has a copy constructor whose first parameter is of type const M& or const volatile M&. Otherwise, the implicitly-declared copy constructor will have the form

X::X(X&)

That is, when you comment out the declaration holder(holder const&) = default; the implicitly declared copy constructor of holder will have the form holder(holder&). But if you don't, T's copy constructor has take const T& (or const volatile T&) because this is what will be called in memberwise copy procedure described in [class.copy]/15.

And if holder has a move constructor, it's even easier - if you comment out holder(holder const&) = default;, the implicitly declared copy constructor of holder will be just deleted.

like image 195
Anton Savin Avatar answered Nov 03 '22 11:11

Anton Savin