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.
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;
};
}
Related
As far as I know, since C++17 some STL data structures may "exist" with an incomplete type as the template parameter which describes the type stored. For example, I may use std::unique_ptr<Incomplete> (I'm not sure if it's a data structure though) or std::vector<Incomplete> as class members if all properties of the class (which need Incomplete's definition) are implemented in a separate .cpp file:
class Incomplete;
using Complete = int;
class Foo {
private:
std::unique_ptr<Incomplete> u_p;
std::vector<Incomplete> v;
std::deque<Incomplete> d;
std::list<Incomplete> l;
std::set<Incomplete> s;
std::unordered_map<Complete, Complete> u_m_cc;
std::unordered_map<Complete, Incomplete> u_m_ci;
std::unordered_map<Incomplete, Complete> u_m_ic;
std::unordered_map<Incomplete, Incomplete> u_m_ii;
public:
// implemented in a separate .cpp which has Incomplete defined:
Foo();
Foo(Foo&&);
Foo& operator=(Foo&&);
Foo(Foo const&);
Foo& operator=(Foo const&);
~Foo();
};
So, which of the data members listed above are valid for such usage? What about other data structures, smart pointers etc.?
Assuming none of the classes members are used explicitly or implicitly until the type is complete:
The template argument can always be incomplete for std::unique_ptr and std::shared_ptr since C++11, see [unique.ptr]/5 and [util.smartptr.shared]/2 respectively.
Support of incomplete types in containers was added with N4510 to C++17, but only for
std::vector
std::list
std::forward_list
and only if the allocator used fulfills the allocator completeness requirements, namely that, even if the value type itself is not complete, the allocator type X itself is a complete type and so are all members of std::allocator_traits<X>, except ::value_type. The default allocator std::allocator fulfills these requirements.
None of the other containers can be used with incomplete types. According to the proposal linked above the scope was limited to these three containers "as a first step" because the major implementations already had support for it.
since C++17 some STL data structures may "exist" with an incomplete type as the template parameter which describes the type stored.
This is incorrect.
Since C++17, some STL types may be declared with an incomplete type as the template parameter.
By the time the types are instantiated, the types must be complete.
For example: (untested code)
struct T; // incomplete
using TV = std::vector<T>; // declared a type using incomplete type T; fine.
TV tv0; // attempt to declare a variable of type TV; fails to compile.
struct T { int v; }; // T is now complete
TV tv1; // compiles
Consider the following code snippet, where the first line serves only as forward declaration
class A;
followed by defining new class
class B
{
vector<A> Av; //line 1
map<int, A> Am; //line 2
pair<int, A> Ap; //line 3
};
line 1 and line 2 seems to be fine with the forward declaration (which may tell me that those container use pointer type of implementation) where as line 3 does not seem to compile on VS2012.
My question is that behavior dictated by the standard or specific to the compiler I am using?
The relevant rules for the standard library types are in [res.on.functions]:
In particular, the effects are undefined in the following cases: [...] if an incomplete type (3.9) is used as a template argument when instantiating a template component, unless specifically allowed for that component.
This:
vector<A> Av;
is fine. std::vector is allowed to be instantiated with an incomplete type, as long as it becomes complete before you use any of the members. There is an explicit exception for this in the standard in [vector.overview]:
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.
There is similar wording for std::list and std::forward_list.
This:
map<int, A> Am;
is ill-formed. std::map requires a complete type at point of instantiation as per the first quote. There is no exception for this container in the way that there is for vector.
This:
pair<int, A> Ap;
cannot possibly ever work, since pair is just a simply struct with two members. In order to have a member of type A, you need a complete type.
[As supplemental instruction to Barry's answer]
According to the standard (C++17), only std::vector, std::list and std::forward_list could be used with incomplete type when instantiating.
§23.3.11.1/3 Class template vector overview
[vector.overview]:
An incomplete type T may be used when instantiating vector if the allocator satisfies the allocator completeness requirements [allocator.requirements.completeness]. T shall be complete before any member of the resulting specialization of vector is referenced.
§23.3.9.1/4 Class template forward_list overview
[forwardlist.overview]:
An incomplete type T may be used when instantiating forward_list if the allocator satisfies the allocator completeness requirements [allocator.requirements.completeness]. T shall be complete before any member of the resulting specialization of forward_list is referenced.
§23.3.10.1/3 Class template list overview
[list.overview]:
An incomplete type T may be used when instantiating list if the allocator satisfies the allocator completeness requirements [allocator.requirements.completeness]. T shall be complete before any member of the resulting specialization of list is referenced.
Nope, this behavior is expected and standard.
The rational is that std::pair actually forms a struct, therefore both its types must be complete before instantiation.
struct some_struct
{
std::list<some_struct> nested;
};
IIUC in the past this would invoke UB because the standard containers required to be instantiated only with complete types.
Now, however, we have progressed from this and as per http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4510.html we can instantiate std::list, std::forward_list and std::vector with incomplete types, so the above code is valid.
But, but, may I go further a little and write:
struct some_struct
{
std::list<some_struct>::iterator it;
};
?
Instantiating a container and using its member types is not the same... For example, I was told that I may instantiate boost containers with incomplete types, but I may not use their value_type unless the template argument is a complete type. So I'm not really sure if the above code snippet is valid.
Is it?
That very paper says
T shall be complete before any member of the resulting specialization
of list is referenced.
Recently I am having many problem with typedef and incomplete type when I changed certain containers, allocators in my code.
What I had previously
struct foo;//incomplete type.
typedef std::vector<foo> all_foos;
typedef all_foos::reference foo_ref;
Though not completely not sure whether the above lines are legal, but this worked on every implementation I used. When I thought that I can do the job with std::tr1::array, changed the above two lines with
typedef std::tr1::array<foo,5> all_foos;
typedef all_foos::reference foo_ref;
Here everything breaks, as the compiler tries to instantiate array and fails as foo is incomplete type. What all I needed is a reference to foo, and not much interested on 'other parts' of the array. foo will definitely be completely available when I create such an array.
The same is problem when typedef std::allocator<foo>::pointer foo_ptr got replaced by typedef stack_alloc<foo,10>::pointer foo_ptr. where a stack_alloc implementation is like
template<typename T,unsigned N>
struct stack_alloc
{
typedef T* pointer;
typedef std::tr1::aligned_storage<sizeof(T)*N, std::tr1::alignment_of<T>::value> buffer;
};
Presuming that, value_type, pointer, reference, iterator etc does not depend on the completeness of T, and knowing that the class can not be instantiate without complete type, how such typedef can be made in generic way independent of specific container or allocator?
NOTE:
Just for completeness, in 'real' code I use a small local memory with vector rather than replacing it with std::array, though the problem remains same.
stack_alloc code is far from complete, and only shows the part of the problem.
I know that array, sizeof etc needs complete type available. But I am NOT creating object of type all_foos with incomplete foo.
My assertion is that pointer,reference etc should not depend on completeness of a type. Otherwise construct like struct foo{ foo_ptr p;}; can not be defined. Though probably foo_ref can not be anything other than foo&, but foo_ptr can be. Surprisingly GCC implementation doesn't have nested pointer type for tr1::array.
Know mostly what can not be done, and interested to know what can be done in this situation. So expecting a good design as a solution.
A type must be complete to be used in a standard container, or the behavior is undefined (§17.4.3.6/2). So the only standard solution is to not make that typedef until the class is defined.
I don't get what the intermediate container is for:
struct foo;//incomplete type.
typedef foo& foo_ref;
In any case, you'll just have to have the complete type defined first, really. To get a typedef defined in a class, that class must be instantiated, which means the entire thing must be able to use T as desired.
For example, stack_alloc must have T be a complete type (for sizeof(T) to work), otherwise the class cannot be instantiated. If the class can't be instantiated, you cannot get the typedef out of it. Ergo, you'll never get the typedef out of it if T is incomplete.
Compiller doesn't know the size of incomplete type, therefore it can not instantiate it nor allocate some memory for it. Having a pointer to object (like typedef std::tr1::array<foo*, 5> all_foos; ) instead of instance of the object itself solves this issue.
Recently I am having many problem with typedef and incomplete type when I changed certain containers, allocators in my code.
What I had previously
struct foo;//incomplete type.
typedef std::vector<foo> all_foos;
typedef all_foos::reference foo_ref;
Though not completely not sure whether the above lines are legal, but this worked on every implementation I used. When I thought that I can do the job with std::tr1::array, changed the above two lines with
typedef std::tr1::array<foo,5> all_foos;
typedef all_foos::reference foo_ref;
Here everything breaks, as the compiler tries to instantiate array and fails as foo is incomplete type. What all I needed is a reference to foo, and not much interested on 'other parts' of the array. foo will definitely be completely available when I create such an array.
The same is problem when typedef std::allocator<foo>::pointer foo_ptr got replaced by typedef stack_alloc<foo,10>::pointer foo_ptr. where a stack_alloc implementation is like
template<typename T,unsigned N>
struct stack_alloc
{
typedef T* pointer;
typedef std::tr1::aligned_storage<sizeof(T)*N, std::tr1::alignment_of<T>::value> buffer;
};
Presuming that, value_type, pointer, reference, iterator etc does not depend on the completeness of T, and knowing that the class can not be instantiate without complete type, how such typedef can be made in generic way independent of specific container or allocator?
NOTE:
Just for completeness, in 'real' code I use a small local memory with vector rather than replacing it with std::array, though the problem remains same.
stack_alloc code is far from complete, and only shows the part of the problem.
I know that array, sizeof etc needs complete type available. But I am NOT creating object of type all_foos with incomplete foo.
My assertion is that pointer,reference etc should not depend on completeness of a type. Otherwise construct like struct foo{ foo_ptr p;}; can not be defined. Though probably foo_ref can not be anything other than foo&, but foo_ptr can be. Surprisingly GCC implementation doesn't have nested pointer type for tr1::array.
Know mostly what can not be done, and interested to know what can be done in this situation. So expecting a good design as a solution.
A type must be complete to be used in a standard container, or the behavior is undefined (§17.4.3.6/2). So the only standard solution is to not make that typedef until the class is defined.
I don't get what the intermediate container is for:
struct foo;//incomplete type.
typedef foo& foo_ref;
In any case, you'll just have to have the complete type defined first, really. To get a typedef defined in a class, that class must be instantiated, which means the entire thing must be able to use T as desired.
For example, stack_alloc must have T be a complete type (for sizeof(T) to work), otherwise the class cannot be instantiated. If the class can't be instantiated, you cannot get the typedef out of it. Ergo, you'll never get the typedef out of it if T is incomplete.
Compiller doesn't know the size of incomplete type, therefore it can not instantiate it nor allocate some memory for it. Having a pointer to object (like typedef std::tr1::array<foo*, 5> all_foos; ) instead of instance of the object itself solves this issue.