1
votes

First some background. As the C++17 standard says:

[vector.overview]/3 An incomplete type T may be used when instantiating vector if the allocator satisfies the allocator completeness requirements 17.6.3.5.1. T shall be complete before any member of the resulting specialization of vector is referenced.

I have tried 3 scenarios in this repo (code copied at the bottom):

  1. The class containing the incomplete vector type is declared (default ctor/dtor) and defined in the same header file << Compilation Succeeds
    • compiling with a.h included: clang++ test.cpp --std=c++17
  2. The class containing the incomplete vector type is declared (default ctor/dtor) and defined in header and source files << Compilation Fails
    • compiling with b.h included: clang++ test.cpp b.cpp --std=c++17
  3. The class containing the incomplete vector type is declared and defined in header and source files (ctor/dtor explicitly defined) << Compilation Succeeds
    • compiling with b.h included: clang++ test.cpp c.cpp --std=c++17

My question is, why does the compilation fail in the second case but not the first or the third? If as the standard says a member of std::vector is referenced, how come it is not referenced in the header only case? And what is this referenced member? Is there any way I can have the second case compile without touching the default keyword or the forward declaration?

P.S. I have tried with clang 9.0.0 and Apple clang version 11.0.0.

main.cpp

// #include "a.h" // << OK
#include "b.h" // << Compile error
// #include "c.h" // << OK

int main(int argc, char const *argv[])
{
    Bar b;
    return 0;
}

a.h

#include <vector>

struct Foo;

struct Bar
{
    Bar() = default;
    virtual ~Bar() = default;
    std::vector<Foo> foos;
};

struct Foo
{
};

b.h

#include <vector>
struct Foo;
struct Bar
{
    Bar() = default;
    ~Bar() = default;
    std::vector<Foo> foos;
};

b.cpp

#include "b.h"
struct Foo
{
};

c.h

#include <vector>

struct Foo;
struct Bar
{
    Bar();
    ~Bar();
    std::vector<Foo> foos;
};

c.cpp

#include "c.h"

struct Foo
{
};

Bar::Bar(){}
Bar::~Bar(){}

Compilation output for the case with error:

(py3) cpp/vector_incomplete > clang++ test.cpp b.cpp --std=c++17
In file included from test.cpp:2:
In file included from ./b.h:1:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:427:68: error: 
      arithmetic on a pointer to an incomplete type 'Foo'
        __alloc_traits::destroy(__alloc(), _VSTD::__to_raw_pointer(--__soon_to_be_end));
                                                                   ^ ~~~~~~~~~~~~~~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:370:29: note: in
      instantiation of member function 'std::__1::__vector_base<Foo, std::__1::allocator<Foo> >::__destruct_at_end' requested here
    void clear() _NOEXCEPT {__destruct_at_end(__begin_);}
                            ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:464:9: note: in
      instantiation of member function 'std::__1::__vector_base<Foo, std::__1::allocator<Foo> >::clear' requested here
        clear();
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:496:5: note: in
      instantiation of member function 'std::__1::__vector_base<Foo, std::__1::allocator<Foo> >::~__vector_base' requested here
    vector() _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
    ^
./b.h:6:5: note: in instantiation of member function 'std::__1::vector<Foo, std::__1::allocator<Foo> >::vector' requested here
    Bar() = default;
    ^
./b.h:3:8: note: forward declaration of 'Foo'
struct Foo;
       ^
In file included from test.cpp:2:
In file included from ./b.h:1:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:275:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__bit_reference:16:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/algorithm:644:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1817:55: error: 
      invalid application of 'sizeof' to an incomplete type 'Foo'
        {_VSTD::__libcpp_deallocate((void*)__p, __n * sizeof(_Tp), _LIBCPP_ALIGNOF(_Tp));}
                                                      ^~~~~~~~~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1555:14: note: in
      instantiation of member function 'std::__1::allocator<Foo>::deallocate' requested here
        {__a.deallocate(__p, __n);}
             ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:465:25: note: in
      instantiation of member function 'std::__1::allocator_traits<std::__1::allocator<Foo> >::deallocate' requested here
        __alloc_traits::deallocate(__alloc(), __begin_, capacity());
                        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:496:5: note: in
      instantiation of member function 'std::__1::__vector_base<Foo, std::__1::allocator<Foo> >::~__vector_base' requested here
    vector() _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
    ^
./b.h:6:5: note: in instantiation of member function 'std::__1::vector<Foo, std::__1::allocator<Foo> >::vector' requested here
    Bar() = default;
    ^
./b.h:3:8: note: forward declaration of 'Foo'
struct Foo;
       ^
In file included from test.cpp:2:
In file included from ./b.h:1:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:373:52: error: 
      arithmetic on a pointer to an incomplete type 'Foo'
        {return static_cast<size_type>(__end_cap() - __begin_);}
                                       ~~~~~~~~~~~ ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:465:57: note: in
      instantiation of member function 'std::__1::__vector_base<Foo, std::__1::allocator<Foo> >::capacity' requested here
        __alloc_traits::deallocate(__alloc(), __begin_, capacity());
                                                        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:496:5: note: in
      instantiation of member function 'std::__1::__vector_base<Foo, std::__1::allocator<Foo> >::~__vector_base' requested here
    vector() _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
    ^
./b.h:6:5: note: in instantiation of member function 'std::__1::vector<Foo, std::__1::allocator<Foo> >::vector' requested here
    Bar() = default;
    ^
./b.h:3:8: note: forward declaration of 'Foo'
struct Foo;
       ^
In file included from test.cpp:2:
In file included from ./b.h:1:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:873:54: error: 
      arithmetic on a pointer to an incomplete type 'const std::__1::vector<Foo, std::__1::allocator<Foo> >::value_type'
      (aka 'const Foo')
      __annotate_contiguous_container(data(), data() + capacity(),
                                              ~~~~~~ ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:552:9: note: in
      instantiation of member function 'std::__1::vector<Foo, std::__1::allocator<Foo> >::__annotate_delete' requested here
        __annotate_delete();
        ^
./b.h:6:5: note: in instantiation of member function 'std::__1::vector<Foo, std::__1::allocator<Foo> >::~vector' requested here
    Bar() = default;
    ^
./b.h:3:8: note: forward declaration of 'Foo'
struct Foo;
       ^
4 errors generated.

2
A code listing of the failing code, with the compiler errors would be useful. - robthebloke
Questions should include relevant code directly, not behind links. See How to Ask. - walnut
stackoverflow.com/q/44664807/1116364 an old question of mine which might help? - Daniel Jour
@DanielJour Thanks. I have seen your question. I still don't understand why I am getting the compilation error when I default the ctor and define Foo in a separate file but not in the two other cases. - pooya13
Think about where ~Bar gets instantiated: does the compiler have access to the size of Foo anywhere in that translation unit? (compiling the 2 files in 2 separate commands may be clearer) - Marc Glisse

2 Answers

0
votes

including b.h won't work. Somewhere within the std::vector template will be a line that allocates memory for NUM * sizeof(Foo). If you bury the definition of Foo within the source file, then the size of Foo will be unknown, and compilation will fail.

0
votes

It fails because you have defaulted constructor and destructor, that means the implementation is in each compilation unit, just as you would have written.

struct Bar
{
    Bar() {}
    ~Bar() {}
    std::vector<Foo> foos;
};

And then the compiler needs to know the size of Foo and wants to call its destructor.

But if you bring the implementation of the functions in the .cpp and only have the declaration in the header you can savely compile the main.cpp because everything the compiler needs to know is there.

You can also (and I would recommend it) default the functions within c.cpp:

#include "c.h"

struct Foo
{
};

Bar::Bar() = default;
Bar::~Bar() = default;