简体   繁体   中英

Confusion (or Clang bug?) about incomplete types in std::vector

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:

  • GCC 12, Clang 14 and Clang 15 in C++17-mode still compile this
  • Clang 15 in C++20-mode fails with this error:
    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:

  • All the other compilers (see above) do not even warn
  • The Thrift C++ compiler actually produces code that does exactly this.

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.


Edit 1: I don't think this is a duplicate

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::map::reverse_iterator doesn't work with C++20 when used with incomplete type

  • The std::map specification never says (contrary to the std::vector specification, see [vector.overview]/4) that it can be instantiated with an incomplete type. Thus, the problem in my question (with std::vector) is not the same as the problem with std::map
  • In the question, an object of the offending std::map is instantiated before the incomplete type was made complete - this can obviously never work. In my question, no object is instantiated at all.

What C++20 change to reverse_iterator is breaking this code?

  • Here, a member of 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.
  • Again, an object of the vector-with-incomplete-type is instantiated before the type was made complete, which cannot work and which I'm also not trying to do.

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 (ie 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> , ie , 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).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM