Forward declaration of objects with STL containers - c++

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.

Related

Why does std::map work, but std::unordered_map throw an error when used in a struct [duplicate]

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

How can I declare a member vector of the same class?

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;
};
}

May I use iterator of a list of an incomplete type?

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.

May I instantiate a templated object A<B> containing a deque of objects of type A<B>?

I would like to understand if the following code is correct in general, or not:
#include <deque>
template<class T>
struct Node {
std::deque<Node<T> > branches;
T data;
Node(const T& _data) : data(_data) {
}
};
void dummy() {
Node<float> test(5.0f);
}
This code was compiled with several toolchains without ever generating errors (See here for example).
The problem is that now I get an instantiation error (maybe related to the fact that I am using llvm libc++, not sure about the version).
<...>llvm-libc++/include/deque:912:55: error: invalid application of 'sizeof' to incomplete type 'std::__1::__deque_base<Node<float>, std::__ 1::allocator<Node<float> > >::value_type {aka Node<float>}'
static const difference_type __block_size = sizeof(value_type) < 256 ? 4096 / sizeof(value_type) : 16;
In case this code is correct, I am not interested to investigate the origin of the bug (compiler or std library implementation) or to have a workaround: again, I mainly would like to understand if the code above is formally correct.
The code is ill-formed. At this point:
template<class T>
struct Node {
std::deque<Node<T> > branches; // <==
Node<T> is still an incomplete type. There are currently built-in exceptions for incomplete types for three of the containers:
"An incomplete type T may be used when instantiating forward_list 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 forward_list is referenced." [forwardlist.overview]/4
"An incomplete type T may be used when instantiating list 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 list is
referenced." [list.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." [vector.overview]/3
There is no such wording for deque, which currently requires a complete type. Seems inconsistent to me to allow incomplete types for vector but not deque, but that's the way it is.
It's not a bug in your compiler nor the standard library. The program is ill formed. You may not instantiate std::deque with an incomplete value type.

How can an incomplete type be used as a template parameter to vector here?

TIL the following program is legal and whatnot:
#include <vector>
struct Bar;
struct Foo
{
using BarVec = std::vector<Bar>::size_type;
};
struct Bar {};
int main()
{
Foo f;
}
How? Bar is an incomplete type so the compiler has no way of knowing what std::vector<Bar> is, or that it contains a member size_type, or that the member size_type is a type.
The only explanation I can come up with is that any hypothetical specialisation would (presumably) have to already be in scope to cause size_type to take on a meaning different from that given in the "base" template definition, and size_type is not a dependent name (both factors contributing to the compiler's certainty).
What's the legal rationale here?
I think in practice this may work but from what I can tell this looks like undefined behavior. From the draft C++11 standard 17.6.4.8 [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.
Although instantiating a template component does not seem like a well-defined term.
I came to this via LWG defect 611 which added:
unless specifically allowed for the component.
to the end of the bullet above so it now reads:
if an incomplete type (3.9) is used as a template argument when instantiating a template component, unless specifically allowed for the component.
as an exception for shared_ptr since the above quote conflicted with this quote from 20.6.6.2 [util.smartptr.shared]:
The template parameter T of shared_ptr may be an incomplete type.
Also see N4371: Minimal incomplete type support for standard containers, revision 2.