19

The very simple code below compiles and links without a warning in C++98 but gives an incomprehensible compile error in C++11 mode.

#include <map>

struct A {
    A(A& ); // <-- const missing
};

int main() {
    std::map<int, A> m;
    return m.begin() == m.end(); // line 9
}

The error with -std=c++11 is, gcc version 4.9.0 20140302 (experimental) (GCC):


ali@X230:~/tmp$ ~/gcc/install/bin/g++ -std=c++11 cctor.cpp 
In file included from /home/ali/gcc/install/include/c++/4.9.0/bits/stl_algobase.h:64:0,
                 from /home/ali/gcc/install/include/c++/4.9.0/bits/stl_tree.h:61,
                 from /home/ali/gcc/install/include/c++/4.9.0/map:60,
                 from cctor.cpp:1:
/home/ali/gcc/install/include/c++/4.9.0/bits/stl_pair.h: In instantiation of ‘struct std::pair’:
cctor.cpp:9:31:   required from here
/home/ali/gcc/install/include/c++/4.9.0/bits/stl_pair.h:127:17: error: ‘constexpr std::pair::pair(const std::pair&) [with _T1 = const int; _T2 = A]’ declared to take const reference, but implicit declaration would take non-const
       constexpr pair(const pair&) = default;
                 ^

with clang version 3.5 (trunk 202594)

ali@X230:~/tmp$ clang++ -Weverything -std=c++11 cctor.cpp 
In file included from cctor.cpp:1:
In file included from /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/map:60:
In file included from /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/bits/stl_tree.h:63:
In file included from /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/bits/stl_algobase.h:65:
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/bits/stl_pair.h:119:17: error: the parameter for this explicitly-defaulted copy constructor is const, but
      a member or base requires it to be non-const
      constexpr pair(const pair&) = default;
                ^
cctor.cpp:9:22: note: in instantiation of template class 'std::pair' requested here
    return m.begin() == m.end(); // line 9
                     ^
1 error generated.

I have been looking at the code in bits/stl_tree.h and I don't understand why it is trying to instantiate std::pair.

Why does it need the copy constructor of std::pair in C++11?


Note: the above code was extracted from Equality operator (==) unsupported on map iterators for non-copyable maps.


SOLUTION

There are two unfortunate issues here.

Poor quality error messages: Line 8 should already give a compile error although the error messages are only complaining about line 9 . Getting an error on line 8 would be quite helpful and understanding the real problem would be much easier. I will probably submit a bug report / feature request if this issue is still present in gcc / clang trunk.

The other issue is what ecatmur writes. Consider the following code:

struct A {
    A() = default;
    A(A& ); // <-- const missing
};

template<class T>
struct B {
    B() = default;
    B(const B& ) = default;
    T t;
};

int main() {
  B<A> b;  
}

It fails to compile. Even though the copy constructor is not needed anywhere, it is still instantiated because it is defaulted inline, in the body of the class; this leads to the compile error. This can be fixed by moving the copy constructor out of the body of the class:

template<class T>
struct B {
    B() = default;
    B(const B& );
    T t;
};

template <class T>
B<T>::B(const B& ) = default;

Everything is OK then. Unfortunately, std::pair has a default defined inline copy constructor.

Community
  • 1
  • 1
Ali
  • 56,466
  • 29
  • 168
  • 265
  • 1
    Hmm at coliru it already fails when the map is just defined, no need to include the comparison or `begin`/`end`. – dyp Mar 12 '14 at 18:03
  • in this example too, [it fails if map is just declared](http://ideone.com/IFdioE) – 4pie0 Mar 12 '14 at 18:04
  • @dyp Correct. That would have been a liitle bit more obvious then. Strange that the error message only barks at the iterator comparison. – Ali Mar 12 '14 at 18:08
  • 1
    [`std::pair p;`](http://ideone.com/oWk2S5) is enough. – Yakk - Adam Nevraumont Mar 12 '14 at 18:30
  • 1
    clang/libc++ has no problem: http://coliru.stacked-crooked.com/a/d9546aed80a496c3 (note that your output indicates that you're using clang with gcc 4.7 library) – Cubbi Mar 12 '14 at 21:52
  • @Cubbi Ah, thanks, I was wondering what clang would do with libc++. Please [read here](http://stackoverflow.com/questions/22357887/comparing-two-mapiterators-why-does-it-need-the-copy-constructor-of-stdpair?noredirect=1#comment33989818_22359998). It's good to know that I can test clang with libc++ on Coliru, I did not know that. – Ali Mar 12 '14 at 21:59

3 Answers3

4

The copy constructor of std::pair isn't needed in this case, but because it is default defined inline in the declaration of std::pair, it is automatically instantiated along with the instantiation of std::pair itself.

It would be possible for the standard library to provide a non-inline default definition of the copy constructor:

template<class _T1, class _T2>
  struct pair
  {
// ...
    constexpr pair(const pair&);
// ...
  };
// ...
template<class _T1, class _T2>
constexpr pair<_T1, _T2>::pair(const pair&) = default;

However this would not accord with the strict letter of the standard (clause 20.3.2), where the copy constructor is default defined inline:

  constexpr pair(const pair&) = default;
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Okay, thanks. Looks like this is the answer. Just one more question: Is it allowed by the standard that the compiler only barks at the iterator comparison? If I comment out the `return m.begin() == m.end();` line then it is obvious that it cannot instantiate the map itself. Barking at the iterator comparison is just confusing... :( – Ali Mar 12 '14 at 18:13
  • @Ali the error in the instantiation of the map itself is delayed because it goes through a non-inline member function (`_Rb_tree<...>::_M_erase(...)`). Unfortunately the standard doesn't require that error messages ("diagnostics") be particularly useful; compilers have gotten quite a bit better at this recently (it's called QOI, or quality of implementation). – ecatmur Mar 12 '14 at 18:28
  • 2
    Sorry, why is it automatically instantiated? For other `template` methods, even if the function is written inside the class, it is only instantiated if called. Is there verbage saying that `=default` instantiates without being called now? – Yakk - Adam Nevraumont Mar 12 '14 at 18:28
  • @ecatmur OK, let me rephrase that: Is it allowed that the diagnostic complains only about the second line but not the first line that wouldn't compile? I have always thought that at least the first line that fails is in the error message. – Ali Mar 12 '14 at 18:35
  • @Ali no, it's entirely up to the compiler the order in which to emit error messages. – ecatmur Mar 12 '14 at 18:49
  • @Yakk it seems that clang and g++ are special casing copy constructors - I can't even find any verbiage to say that it's an error to default a copy constructor that can't be implicitly defined (8.4.2p4 just says the end up defined as deleted). Could it be a bug (in both compilers)? – ecatmur Mar 12 '14 at 18:51
  • @ecatmur Thanks. *"Could it be a bug (in both compilers)?"* I don't know if it matters: They both use `libstdc++`. I wonder what clang would do with `libc++`. Please try to find out if it is a bug or not. Even if it isn't, I will submit a bug report / feature request to improve that error message because it is a rather confusing one. – Ali Mar 12 '14 at 19:10
  • @Ali Coliru is using an old version of libc++ that doesn't implement the `pair` copy constructor with `= default`; if I use a newer version of libc++ then clang exhibits the same issue as gcc/libstdc++. – ecatmur Mar 13 '14 at 21:10
  • @ecatmur I see, thanks for the info. *"it seems that clang and g++ are special casing copy constructors - I can't even find any verbiage to say that it's an error to default a copy constructor that can't be implicitly defined (8.4.2p4 just says the end up defined as deleted). Could it be a bug (in both compilers)?"* Did you manage to find out whether it is a bug or not? – Ali Mar 13 '14 at 21:13
  • @Yakk I think 8.4.2p2 is what's causing the issue here: "*If a function is explicitly defaulted on its first declaration, [...] a copy constructor [...] shall have the same parameter type as if it had been implicitly declared*". So the compiler is required to check that the parameter type is "correct", even if it doesn't end up calling the copy constructor. – ecatmur Mar 13 '14 at 21:36
  • @Ali it looks like the compilers are following the standard correctly, unfortunately. – ecatmur Mar 13 '14 at 21:39
  • @ecatmur I apologize that it took so long; I have been extremely busy lately. I still have questions regarding this issue, there are still peculiar things but your answer answers the question as posted. I will probably post new questions if I have time to investigate the details. Thanks for the great answer and sorry for the long delay! – Ali Apr 16 '14 at 14:48
2

std::map uses std::pair to store key-value pairs, where the key (the first element) is const.

The compiler error relates to the required copy constructor for std::pair, even if it isn't being used (which I don't think it is).

std::pair<int, A> has to be generated. This is first required with the call to map::begin. Since no explicit copy constructor is given for this type, the implicit one used.

The implicit constructor will have signature T::T(const T&) only if all non-static members of T, (type S), have copy constructors S::S(const S&) (the same requirement has to hold for T's base types copy constructors). Otherwise a copy constructor with signature T::T(T&) is used instead.

A's copy constructor fails this requirement, so std::pair::pair has the wrong signature for the STL, which requires T::T(const T&).

xen-0
  • 709
  • 4
  • 12
2

I think I found it after trying to reduce the error. First, the comparison doesn't seem required to make the program ill-formed. Then, the error message contained the dtor, so I tried not to instantiate the dtor. Result:

#include <map>

struct A {
    A(A& ); // <-- const missing
};

int main() {
    std::map<int, A>* m = new std::map<int, A>();
    // note: dtor not (necessarily?) instantiated
}

But the output message still contains, now for the line where the ctor of m is called:

error: the parameter for this explicitly-defaulted copy constructor is const, but a member or base requires it to be non-const

 constexpr pair(const pair&) = default;

Which hints to [dcl.fct.def.default]/4

A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed.

[emphasis mine]

If, as I assume, [class.copy]/11 says that this ctor should be defined as deleted, then it is defined as deleted immediately - not only when it's odr-used. Therefore, an instantiation shouldn't be required to make the program ill-formed.

Community
  • 1
  • 1
dyp
  • 38,334
  • 13
  • 112
  • 177
  • I think [class.copy]/11 should imply that this function is deleted; however although it mentions overload resolution, it does not explicitly say that the function must be viable. – dyp Mar 12 '14 at 18:17
  • Okay, thanks. If you look at the error messages I posted, you will see that those messages also contain the same error message as you get. One more question: In case of the code posted in the question, is it allowed by the standard that the compiler only barks at the iterator comparison? – Ali Mar 12 '14 at 18:18
  • @Ali Hmm I think once the program is ill-formed, only *one* diagnostic message is required (and I don't think there are requirements on its content). – dyp Mar 12 '14 at 18:22
  • I would have expected to get an error message for the very first line that fails to compile. I understand that only one error message is required but is it allowed to complain only about the *second* problematic line? This leads to confusions and find it a very poor error message. – Ali Mar 12 '14 at 18:28
  • @Ali I agree the diagnostic should be improved. But as I said, the Standard doesn't impose any requirements on the contents of the diagnostic message, [intro.compliance]/2 (line number <= content) – dyp Mar 12 '14 at 18:31
  • 1
    Alright then. I will probably submit a bug report / feature request because I find it very confusing. – Ali Mar 12 '14 at 18:36