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.
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
The struct Vec has all the requirements to be std::default_initializable. But the declaration is not finished, so it failed to compile.
template <std::default_initializable A>
struct It {};
struct Vec {
using Iterator = It<Vec>;
};
Is there some sort of workaround to keep the It requirement ?
In general, the answer to your question is no. If you want to make a member type alias, it has to be known at the time in question. And if the template you're trying to instantiate requires that the type it is given is complete (which default-initializable does), then it must be complete.
In your specific case however, it's not really necessary to make Iterator a member. If you want the iterator type for a range, the correct way to do that is to use ranges::iterator_t. And that meta-function will return the type that ranges::begin() returns.
Your begin member function can be specified to return auto, such that the definition of the function is what provides the It<Vec>, as follows:
template <std::default_initializable A>
struct It {};
struct Vec
{
auto begin() {return It<Vec>(...);}
};
That having been said, even if you want to define your iterator type outside of the container/range that it serves, it is still bound to that container/range type. Unless It is some kind of view or range transformation (and if it is, it probably shouldn't be default-constructing the range it modifies), It<UserType> shouldn't work. As such, constraining the It template is pointless; you know that Vec is default constructible because you wrote that class just below the iterator. If you want a sanity check, you can use a static_assert, but you don't need to constrain the template itself.
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.