11
votes

Suppose you have the following class:

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

int main() {
    std::pair<A, int> p1;
    return 0;
}

The following code will fail to compile (using -std=c++11 with g++) with the following error:

/usr/include/c++/5/bits/stl_pair.h: In instantiation of ‘struct std::pair’:

test.cpp:13:23: required from here

/usr/include/c++/5/bits/stl_pair.h:127:17: error: ‘constexpr std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2>&) [with _T1 = A; _T2 = int]’ declared to take const reference, but implicit declaration would take non-const

constexpr pair(const pair&) = default;

According to the error message, I would assume that this is because it is not possible to instanciate the default copy constructor because of the const qualifier on the std::pair argument.

I could understand why this would not compile without the = delete, because it is not possible to instanciate the copy constructor which takes a std::pair const& parameter.

But with the = delete, I would expect the compiler to not instantiate such constructor because it cannot (as far as I understand). Actually, this copy constructor is deleted as shown by this piece of code:

std::pair<A, int> p1;
decltype(p1) p2(p1);

Which fails:

test.cpp:11:23: error: use of deleted function ‘constexpr std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2>&) [with _T1 = A; _T2 = int]’

decltype(p1) p2(p1);

Basically, my question is: Why does the compiler fails to instantiate a deleted copy constructor of std::pair?

2

2 Answers

11
votes

[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&)

Therefore, if the copy constructor of std::pair<A, int> were to be implicitly declared, it would have the form pair::pair(pair &).

[dcl.fct.def.default]/1:

A function that is explicitly defaulted shall

  • be a special member function,

  • have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T”, where T is the name of the member function's class) as if it had been implicitly declared, and

  • not have default arguments.

Since the declaration pair(const pair&) = default; doesn't have the same declared function type as the copy constructor that would have been implicitly declared, and neither exception applies, the program is ill-formed.

10
votes

If you want to = delete the copy constructor, the proper form is: A(const A&) = delete;. See how you forgot that const? pair can be used with a non-copyable type, even a non-moveable type.

Your instantiation problem can also be demonstrated with:

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

struct B : A {
    B(const B&) = default;
};

or...

struct B {
    B(const B&) = default;
    A a;
};

Basically, pair = default its copy ctor, and defines it as taking a const pair&, but one one of your types defines it to take a non-const &, so the default generation fails since it can't pass it's const& to your non-const&. Even though yours is = deleteed, it doesn't matter, it doesn't get that far.

A defaulted copy ctor will effectively be deleted if a member or base class is non-copyable. But in order to figure that out, it has to have a function it is able to call (even if that function is deleted). In these cases, it can't pass a const& to a non-const& so it can't even figure out that you have a deleted copy ctor. Conceivably, something in the standard could be written to accommodate this edge case.