Reputation: 2858
Consider the following code:
#include <memory>
#include <vector>
class A
{
private:
std::vector<std::unique_ptr<int>> _vals;
};
int main()
{
A a;
//A a2(a);
return 0;
}
Compiler A compiles this without issue unless I uncomment out the line A a2(a);
at which point it complains about the copy constructor for std::unique_ptr
being deleted, and therefore I can't copy construct A
. Compiler B, however, makes that complaint even if I leave that line commented out. That is, compiler A only generates an implicitly defined copy constructor when I actually try to use it, whereas compiler B does so unconditionally. Which one is correct? Note that if I were to have used std::unique_ptr<int> _vals;
instead of std::vector<std::unique_ptr<int>> _vals;
both compilers correctly implicitly delete both copy constructor and assignment operator (std::unique_ptr
has a explicitly deleted copy constructor, while std::vector
does not).
(Note: Getting the code to compile in compiler B is easy enough - just explicitly delete the copy constructor and assignment operator, and it works correctly. That isn't the point of the question; it is to understand the correct behavior.)
Upvotes: 8
Views: 417
Reputation: 24936
Note: My answer is based on your comment:
[...] it's only on Windows, and only when I explicitly list class A as a DLL export (via, e.g., class __declspec(dllexport) A) that this happens. [...]
On MSDN we can learn that declaring a class dllexport
makes all members exported and required a definition for all of them. I suspect the compiler generates the definitions for all non-delete
d functions in order to comply with this rule.
As you can read here, std::is_copy_constructible<std::vector<std::unique_ptr<int>>>::value
is actually true
and I would expect the supposed mechanism (that defines the copy constructor in your case for export purposes) checks the value of this trait (or uses a similar mechanism) instead of actually checking whether it would compile. That would explain why the bahviour is correct when you use unique_ptr<T>
instead of vector<unique_ptr<T>>
.
The issue is thus, that std::vector
actually defines the copy constructor even when it wouldn't compile.
Imho, a is_copy_constructible
check is sufficient because at the point where your dllexport
happens you cannot know whether the implicit function will be odr-used at the place where you use dllimport
(possibly even another project). Thus, I wouldn't think of it as a bug in compiler B
.
Upvotes: 3
Reputation: 302718
From [class.copy.ctor]/12:
A copy/move constructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used ([basic.def.odr]), when it is needed for constant evaluation ([expr.const]), or when it is explicitly defaulted after its first declaration.
A
's copy constructor is defaulted, so it's implicitly defined only when it is odr-used. A a2(a);
is just such an odr-use - so it's that statement that would trigger its definition, that would make the program ill-formed. Until the copy constructor is odr-used, it should not be defined.
Compiler B is wrong to reject the program.
Upvotes: 9
Reputation: 62553
While I can't confirm this behavior (I do not have access to Windows compiler, and OP claims the bug happens with icc on Windows platform), taking the question at it's face value, the answer is - compiler B has a gross bug.
In particular, implicitly-declared copy constructor is defined as deleted, when ...
T has non-static data members that cannot be copied (have deleted, inaccessible, or ambiguous copy constructors);
https://en.cppreference.com/w/cpp/language/copy_constructor
Thus, conforming compiler must semantically generate deleted copy-constructor, and successfully compile the program since such constructor never called.
Upvotes: 0