50

Why on earth does the following piece of code work?

struct A {
    std::vector<A> subAs;
};

A is an incomplete type, right? If there was a vector of A*s I would understand. But here I don't understand how it works. It seems to be a recursive definition.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
DavitS
  • 632
  • 5
  • 11
  • 9
    Well, at the lowest level, whether this is a "recursive definition" or not will depend on whether `std::vector` class itself contains any subobjects of type `A`. In a typical implementation of `std::vector` there are no direct subobjects of type `A`. In a typical implementation of `std::vector` it will simply contain an `A *` pointer to the controlled sequence. This eliminates data recursion and makes it possible to use incomplete types as arguments. The only question here is whether the language specification allows or requires it. – AnT Jan 28 '17 at 19:04
  • Related to [How can an incomplete type be used as a template parameter to vector here?](http://stackoverflow.com/q/31345193/1708801) – Shafik Yaghmour Jan 29 '17 at 04:06

1 Answers1

40

This paper was adopted into C++17 which allows incomplete types to be used in certain STL containers. Prior to that, it was Undefined Behavior. To quote from the paper:

Based on the discussion on the Issaquah meeting, we achieved the consensus to proceed* with the approach – “Containers of Incomplete Types”, but limit the scope to std::vector, std::list, and std::forward_list, as the first step.

And as for the changes in the standard (emphasis mine):

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.

So, there you have it, if you leave the default std::allocator<T> in place when instantiating the std::vector<T, Allocator>, then it will always work with an incomplete type T according to the paper; otherwise, it depends on your Allocator being instantiable with an incomplete type T.


A is an incomplete type, right? If there was a vector of A*s I would understand. But here I don't understand how it works. It seems to be a recursive definition.

There is no recursion there. In an extremely simplified form, it's similar to:

class A{
    A* subAs;
};

Technically, apart from size, capacity and possibly allocator, std::vector only needs to hold a pointer to a dynamic array of A it manages via its allocator. (And the size of a pointer is known at compile time.)

So, an implementation may look like this:

namespace std{

    template<typename T, typename Allocator = std::allocator<T>>
    class vector{

        ....

        std::size_t m_capacity;
        std::size_t m_size;
        Allocator m_allocator;
        T* m_data;
    };

}
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
WhiZTiM
  • 19,970
  • 3
  • 36
  • 56
  • 6
    I think http://stackoverflow.com/questions/6517231/are-c-recursive-type-definitions-possible-in-particular-can-i-put-a-vectort should be updated :) – kennytm Jan 28 '17 at 19:03
  • This is a helpful answer, but I am confused by the reference to C++17. This pattern certainly works just fine in my C++14 compiler. – Craig Reynolds Aug 27 '20 at 17:15
  • In earlier versions, support seems to be implementation dependent - recent versions of GCC and Clang accept std::vector with incomplete types even in C++11 mode, but MSVC does not. – Jack Lloyd Nov 05 '20 at 10:03