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.)
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With