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.
Related
I have this code, which works on GCC:
#include <map>
class Foo;
class Bar;
typedef std::map<Foo,Bar> MyMap;
MyMap::iterator i;
class Foo
{
MyMap::iterator some_data;
};
The code as currently designed (which is unpleasantly circular yes I'm stuck with it) requires map<Foo,Bar>::iterator to be available to Foo and Bar.
It works because the GCC library implementation happens to not need to instantiate the map's key type in order to instantiate the iterator.
Is this guaranteed? The standard seems to be somewhat hands-off when it comes to defining the map iterator type. How portable is this code?
This results in undefined behavior.
In the declaration MyMap::iterator i;, MyMap is required to be a complete type, thus it is implicitly instantiated. However, Foo and Bar are not complete at this point of instantiation, so the behavior is undefined according to [res.on.functions]/2:
In particular, the effects are undefined in the following cases:
...
if an incomplete type ([basic.types]) is used as a template argument when instantiating a template component or evaluating a concept, unless specifically allowed for that component.
You can side-step the entire question by considering the fact that std::map is a node-based container, so that its nodes containing elements have stable addresses. I.e. you can use a plain pointer instead of an iterator (as long as you don't need an iterator to pass into member functions of the std::map, of course):
class Foo
{
std::pair<Foo const, Bar>* some_data;
};
Note that only declarations for Foo, Bar and std::pair<> are needed here to define member some_data.
If you use boost::multi_index (which is superior to std associative containers in many ways) then it has extremely useful functions to_iterator that takes a reference to the element and returns an iterator to it.
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;
};
}
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.
Often I see that cpp classes in the beginning of their declaration have this written
typedef base_type data_type
As far as I know, this has something to do with making your classes compatible with vectors. Can anyone guide me?
Googling this will also give you hundreds of links which use this statement.
typedef typename Base<T>::Foo Foo is a common technique for template programming.
Since Base<T> depends on the template parameter T, the compiler can't know if for any particular type T the Base is specialized so that Foo is not a type (or exists at all).
So you have to tell the compiler that it should assume that Foo is a type.
It's a hassle, yes.
But then templates are incredibly useful, so it's well worth a bit of hassle. :)
Other than the above, a typedef just introduces an alternate name with only very subtle differences from the original.
For example, given int x = 42; the statement x.~int(); is invalid, but typedef int Int; x.~Int(); is OK.
Another such subtle difference is for definitions of constructors and destructor, where you must use the original class name.
In C++11 you can use using in much the same way as typedef, e.g. using Int = int;.
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.