Reputation: 3048
The C++20 standard states in [vector.overview]/4:
An incomplete type T may be used when instantiating vector if the allocator meets the allocator completeness requirements. T shall be complete before any member of the resulting specialization of vector is referenced.
The default allocator std::allocate
does satisfy the allocator completeness requirements
. The
main question is what "referenced" means in this context. The code I am confused about are variants of this:
#include <vector>
class MyClass;
class MyContainer
{
std::vector<MyClass> member;
};
class MyClass {};
int main()
{}
The above code compiles fine in all sorts of compilers. It still compiles if I explicitly default the default constructor:
#include <vector>
class MyClass;
class MyContainer
{
MyContainer() = default;
std::vector<MyClass> member;
};
class MyClass {};
int main()
{}
However, when I instead define the default constructor to be "empty", something weird happens. This is the code (here at Compiler Explorer):
#include <vector>
class MyClass;
class MyContainer
{
MyContainer() {};
std::vector<MyClass> member;
};
class MyClass {};
int main()
{}
With this code:
In file included from <source>:1:
In file included from /opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/vector:64:
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/stl_vector.h:367:35: error: arithmetic on a pointer to an incomplete type 'MyClass'
_M_impl._M_end_of_storage - _M_impl._M_start);
~~~~~~~~~~~~~~~~~~~~~~~~~ ^
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/stl_vector.h:526:7: note: in instantiation of member function 'std::_Vector_base<MyClass, std::allocator<MyClass>>::~_Vector_base' requested here
vector() = default;
^
<source>:7:5: note: in defaulted default constructor for 'std::vector<MyClass>' first required here
MyContainer() {};
^
<source>:3:7: note: forward declaration of 'MyClass'
class MyClass;
My first instinct, only looking at the Clang 15 error, was "clang is correct". The default
constructor does (implicitly) invoke the default constructor of std::vector<MyClass>
, and the
standard says that you cannot reference members as long as MyClass
is incomplete.
However, I'm pretty much sure that this cannot be the answer since:
So, my question is: Is this a Clang 15 bug? And if so, is (implicitly) invoking the default
constructor of std::vector<MyClass>
not considered a "reference" in terms of [vector.overview]/4?
I did search the LLVM bug tracker for the terms "vector" and "incomplete", but that did not turn
something up, so if this is a known bug, it's not known in the context of std::vector
, I guess.
This was closed as a duplicate of two questions, which in my opinion, is incorrect. The differences are subtle but relevant. The two questions are:
std::vector<incompleteType>
is explicitly referenced (namely ::reverse_iterator
). This is obviously not covered by [vector.overview]/4. However, I'm not doing that in my question.Upvotes: 8
Views: 628
Reputation: 119457
It's true that "referenced" is not clear here. What I think this sentence probably means is that T
shall be complete before you do anything that would require the definition of any member of the resulting specialization to exist.
In the second example
class MyClass;
class MyContainer
{
MyContainer() = default;
std::vector<MyClass> member;
};
class MyClass {};
the definition of std::vector<MyClass>
's default constructor is not needed until the compiler actually implicitly defines the default constructor of the enclosing class, MyContainer
. And that doesn't happen until the first time MyContainer
's default constructor is either odr-used or needed for constant evaluation, per the last sentence of [dcl.fct.def.default]/5:
A non-user-provided defaulted function (i.e. implicitly declared or explicitly defaulted in the class) that is not defined as deleted is implicitly defined when it is odr-used ([basic.def.odr]) or needed for constant evaluation ([expr.const]).
Your first example, where you didn't declare any default constructor, and your second example, where you declared it as defaulted inside the class definition, are treated similarly: in both cases the constructor is non-user-provided, so it does not get eagerly defined. In both examples, MyContainer
has a non-user-provided copy constructor, move constructor, copy-assignment operator, move-assignment operator, and destructor, which likewise are not defined until their definitions are needed, so the program avoids "referencing" any members of std::vector<MyClass>
.
In the third example, because the constructor is user-provided, it is defined even if it is never used, and its definition implicitly calls the default constructor of std::vector<MyClass>
, i.e., the latter is "referenced".
When you break the rules, as you did in the third example, the program has undefined behaviour. I understand that it seems awfully unfriendly that most compilers you tried don't even warn you, but there is a reason why diagnostics are not required when you misuse incomplete types: it's difficult for templates to check for completeness in a way that does not cause bigger problems (though I believe there is ongoing work on this issue in Clang).
Upvotes: 4