Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

A member of type vector<self>

253 views
Skip to first unread message

Andy Venikov

unread,
Mar 8, 2010, 5:04:09 PM3/8/10
to
I remember there have been quite a few discussions on this topic
and why

class A
{
vector<A> vec_;
};

is actually well-formed under C++98,
but I can't seem to find any of the threads.

I wanted to revisit all the reasoning behind it.

Could someone refresh my memory as to what it's
well-formed or point to one of those all threads?

Thanks,
Andy.

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Pedro Lamarão

unread,
Mar 9, 2010, 12:27:29 AM3/9/10
to
On 8 mar, 19:04, Andy Venikov <swojchelo...@gmail.com> wrote:

> I remember there have been quite a few discussions on this topic
> and why
>
> class A
> {
> vector<A> vec_;
>
> };
>
> is actually well-formed under C++98,
> but I can't seem to find any of the threads.

GCC accepts it:

[pedro.lamarao@elisa Projetos]$ cat test.cpp
#include <vector>

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

[pedro.lamarao@elisa Projetos]$ g++ -c test.cpp
[pedro.lamarao@elisa Projetos]$

IIRC, the template argument to std::vector is allowed to be an
incomplete type.

--
P.

Alf P. Steinbach

unread,
Mar 9, 2010, 12:20:03 AM3/9/10
to
* Andy Venikov:

> I remember there have been quite a few discussions on this topic
> and why
>
> class A
> {
> vector<A> vec_;
> };
>
> is actually well-formed under C++98,

Assuming that 'vector' is 'std::vector' it's not well-formed (see below).


> but I can't seem to find any of the threads.
>
> I wanted to revisit all the reasoning behind it.
>
> Could someone refresh my memory as to what it's
> well-formed or point to one of those all threads?

A type T is "incomplete" at a point where you can't do sizeof(T), otherwise it's
complete; the element type of a standard library container must be complete.

So, for a standard library container type C, if you can do sizeof(E) where E is
the container element type, then at this point you use C<E>, otherwise not.


Cheers & hth.,

- Alf

Kris Prad

unread,
Mar 9, 2010, 8:19:24 AM3/9/10
to
On Mar 9, 5:20 am, "Alf P. Steinbach" <al...@start.no> wrote:
> * Andy Venikov:
>
> > I remember there have been quite a few discussions on this topic
> > and why
>
> > class A
> > {
> > vector<A> vec_;
> > };
>

> So, for a standard library container type C, if you can do sizeof(E) where E is


> the container element type, then at this point you use C<E>, otherwise not.
>
> Cheers & hth.,
>
> - Alf
>

Comeau accepts even this:

#include <vector>


struct A
{
std::vector<A> vec_;

A(): vec_(10) {} // <----- with size
};

BUT NOT THIS:

struct B;
std::vector<B> vec2_; // incomplete type error

Kris

Daniel Krügler

unread,
Mar 9, 2010, 12:35:14 PM3/9/10
to
On 9 Mrz., 14:19, Kris Prad <krisp...@yahoo.co.uk> wrote:
> On Mar 9, 5:20 am, "Alf P. Steinbach" <al...@start.no> wrote:
> > * Andy Venikov:
>
> > > I remember there have been quite a few discussions on this topic
> > > and why
>
> > > class A
> > > {
> > > vector<A> vec_;
> > > };
>
> > So, for a standard library container type C, if you can do sizeof(E) where E is
> > the container element type, then at this point you use C<E>, otherwise not.
>
> > Cheers & hth.,
>
> > - Alf
>
> Comeau accepts even this:
>
> #include <vector>
>
> struct A
> {
> std::vector<A> vec_;
> A(): vec_(10) {} // <----- with size
> };
>
> BUT NOT THIS:
>
> struct B;
> std::vector<B> vec2_; // incomplete type error

It doesn't matter, what Comeau accepts, the C++
standard (both C++03 and C++0x) says that what
you are trying causes undefined behaviour, see
[lib.res.on.functions]/2:

"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."

In your example std::vector<A> will be instantiated,
because vec_ is a non-static data member of A.

The standard does not distinguish here between
instantiation of the class template declaration and
individual member instantiation of that template.
Usually, it "works", but the standard does not
provide fine-grained instantiation guarantees and
in this particular implementation the definition
of the std::vector class does not require T to be
complete. Within the body of any member function
the class is considered as complete anyway,
therefore usage of A will probably be well-defined
for *this particular* implementation.

B is not even complete within the body of the
constructor of vector<B>.

A conforming compiler could quite easily break
either case by adding a static_assert in the
class definition like this:

template<typename T, ...>
class vector {
typedef char __test_complete[sizeof(T)];
...
};

Some (most?) implementations usually don't do
this because user's would like to take advantage
of incomplete types in several scenarios, but this
belongs all to the domain of quality of
implementation.

HTH & Greetings from Bremen,

Daniel Kr�gler

Nick Hounsome

unread,
Mar 9, 2010, 12:38:57 PM3/9/10
to
On 9 Mar, 05:20, "Alf P. Steinbach" <al...@start.no> wrote:
> * Andy Venikov:
>
> > I remember there have been quite a few discussions on this topic
> > and why
>
> > class A
> > {
> > vector<A> vec_;
> > };
>
> > is actually well-formed under C++98,
>
> Assuming that 'vector' is 'std::vector' it's not well-formed (see below).
>
> > but I can't seem to find any of the threads.
>
> > I wanted to revisit all the reasoning behind it.
>
> > Could someone refresh my memory as to what it's
> > well-formed or point to one of those all threads?
>
> A type T is "incomplete" at a point where you can't do sizeof(T), otherwise it's
> complete; the element type of a standard library container must be complete.
>
> So, for a standard library container type C, if you can do sizeof(E) where E is
> the container element type, then at this point you use C<E>, otherwise not.

It is OK because sizeof(A.vec_) is not dependent on sizeof(A)

This is just a tarted up way of saying something like:

struct A
{
struct A* vec_;
};

Bo Persson

unread,
Mar 9, 2010, 12:40:28 PM3/9/10
to
Kris Prad wrote:
> On Mar 9, 5:20 am, "Alf P. Steinbach" <al...@start.no> wrote:
>> * Andy Venikov:
>>
>>> I remember there have been quite a few discussions on this topic
>>> and why
>>
>>> class A
>>> {
>>> vector<A> vec_;
>>> };
>>
>
>> So, for a standard library container type C, if you can do
>> sizeof(E) where E is the container element type, then at this
>> point you use C<E>, otherwise not.
>>
>> Cheers & hth.,
>>
>> - Alf
>>
>
> Comeau accepts even this:
>
> #include <vector>
>
>
> struct A
> {
> std::vector<A> vec_;
> A(): vec_(10) {} // <----- with size
> };
>
> BUT NOT THIS:
>
> struct B;
> std::vector<B> vec2_; // incomplete type error
>
>

This depends on the standard library compiled against. There is no
requirement that it should compile, but it is allowed.


Bo Persson

Johannes Schaub (litb)

unread,
Mar 9, 2010, 9:46:43 PM3/9/10
to
Andy Venikov wrote:

> I remember there have been quite a few discussions on this topic
> and why
>
> class A
> {
> vector<A> vec_;
> };
>
> is actually well-formed under C++98,
> but I can't seem to find any of the threads.
>

It has undefined behavior, but it's well-formed. ("well-formed" means "a C++
program constructed according to the syntax rules, diagnosable semantic
rules, and the One Definition Rule" - the above program fits all that, i
think).

Since it has undefined behavior, it's not exactly nice code and may still
fail to compile.

Andy Venikov

unread,
Mar 9, 2010, 9:46:43 PM3/9/10
to
Alf P. Steinbach wrote:
> * Andy Venikov:
>> I remember there have been quite a few discussions on this topic
>> and why
>>
>> class A
>> {
>> vector<A> vec_;
>> };
>>
>> is actually well-formed under C++98,
>
> Assuming that 'vector' is 'std::vector' it's not well-formed (see below).
>
>
>> but I can't seem to find any of the threads.
>>
>> I wanted to revisit all the reasoning behind it.
>>
>> Could someone refresh my memory as to what it's
>> well-formed or point to one of those all threads?
>
> A type T is "incomplete" at a point where you can't do sizeof(T),
> otherwise it's
> complete; the element type of a standard library container must be
> complete.
>
> So, for a standard library container type C, if you can do sizeof(E)
> where E is
> the container element type, then at this point you use C<E>, otherwise not.

I think it's not the whole story.
If you follow this logic, then it'd be impossible to do Curiously
Recurring Template Pattern as sizeof(Derived) isn't known when you
have to instantiate Base<Derived>.

Now it slowly comes back to me. The trick I think is the two-phase
lookup. If you implement vector in such a way that all your
non-dependent manipulations don't need to know the size of the element,
then by the time second phase kicks in, the size of the element is
known. That's why, I think, it all depends on how vector has been
implemented and not strictly on the language rules.

BTW, comeau is perfectly happy with the code snipped I gave at the
beginning of this post.


Thanks,
Andy.

Alf P. Steinbach

unread,
Mar 10, 2010, 12:24:32 AM3/10/10
to
* Andy Venikov:

> Alf P. Steinbach wrote:
>> * Andy Venikov:
>>> I remember there have been quite a few discussions on this topic
>>> and why
>>>
>>> class A
>>> {
>>> vector<A> vec_;
>>> };
>>>
>>> is actually well-formed under C++98,
>>
>> Assuming that 'vector' is 'std::vector' it's not well-formed (see below).
>>
>>
>>> but I can't seem to find any of the threads.
>>>
>>> I wanted to revisit all the reasoning behind it.
>>>
>>> Could someone refresh my memory as to what it's
>>> well-formed or point to one of those all threads?
>>
>> A type T is "incomplete" at a point where you can't do sizeof(T),
>> otherwise it's
>> complete; the element type of a standard library container must be
>> complete.
>>
>> So, for a standard library container type C, if you can do sizeof(E)
>> where E is
>> the container element type, then at this point you use C<E>, otherwise
>> not.
>
> I think it's not the whole story.
> If you follow this logic, then it'd be impossible to do Curiously
> Recurring Template Pattern as sizeof(Derived) isn't known when you
> have to instantiate Base<Derived>.

The standard imposes the requirement of complete type for the standard library's
container types' element types.

This is not a requirement for your own container types or general templates.

In particular, that requirement does not say anything about the core language,
and hence nothing whatsoever about the Curiously Recurring Template pattern.


> Now it slowly comes back to me. The trick I think is the two-phase
> lookup. If you implement vector in such a way that all your
> non-dependent manipulations don't need to know the size of the element,
> then by the time second phase kicks in, the size of the element is
> known. That's why, I think, it all depends on how vector has been
> implemented and not strictly on the language rules.

Whether it compiles depends on the vector implementation, yes.

If it was not Undefined Behavior then it would not depend on the implementation.


> BTW, comeau is perfectly happy with the code snipped I gave at the
> beginning of this post.

Yes, whether it compiles depends on the implementation.

Cheers & hth.,

- Alf

PS: I skipped adding in discussion of CRT (I actually removed what I'd written
in response to your statements) since, although interesting, it would just be
going off on a tangent, and I thought it best to not mix up the two issues. It's
very easy to get confused when side issues are introduced. So, if you're
interested in CRT then I suggest starting a separate thread.

Johannes Schaub (litb)

unread,
Mar 10, 2010, 12:25:53 AM3/10/10
to
Andy Venikov wrote:

From a core language point of view, a template type argument can be an
incomplete type very well (like "void"). So your observation about CRTP
doesn't mean anything wrt std::vector. You can't instantiate a standard
library "template component" using such an incomplete type.

Notice that it makes a difference if you declare

struct A {
static vector<A> a_; // fine
};

vector<A> A::a_;

That's fine, because vector<A> is not instantiated at "// fine".

> Now it slowly comes back to me. The trick I think is the two-phase
> lookup. If you implement vector in such a way that all your
> non-dependent manipulations don't need to know the size of the element,
> then by the time second phase kicks in, the size of the element is
> known. That's why, I think, it all depends on how vector has been
> implemented and not strictly on the language rules.
>

For a class template that's implicitly instantiated, only member
declarations but not their definitions are implicitly instantiated. If in
their declaration-only part appears no "sizeof(T)" or a concept check for
completeness of T or something, it may go unnoticed.

So i think i have to correct myself wrt whether the code you show is well-
formed or not: It depends on what std::vector does. But it certainly
contains undefined behavior. If it takes the sizeof of T, then that would
make the program ill-formed (however since the program also contained
undefined behavior, no diagnostic is required as said by 1.4/2 last bullet).

> BTW, comeau is perfectly happy with the code snipped I gave at the
> beginning of this post.
>

That's just showing it doesn't do sanity checks (with your config, at
least).

Alf P. Steinbach

unread,
Mar 10, 2010, 12:23:28 AM3/10/10
to
* Nick Hounsome:

> On 9 Mar, 05:20, "Alf P. Steinbach" <al...@start.no> wrote:
>> * Andy Venikov:
>>
>>> I remember there have been quite a few discussions on this topic
>>> and why
>>> class A
>>> {
>>> vector<A> vec_;
>>> };
>>> is actually well-formed under C++98,
>> Assuming that 'vector' is 'std::vector' it's not well-formed (see below).
>>
>>> but I can't seem to find any of the threads.
>>> I wanted to revisit all the reasoning behind it.
>>> Could someone refresh my memory as to what it's
>>> well-formed or point to one of those all threads?
>> A type T is "incomplete" at a point where you can't do sizeof(T), otherwise it's
>> complete; the element type of a standard library container must be complete.
>>
>> So, for a standard library container type C, if you can do sizeof(E) where E is
>> the container element type, then at this point you use C<E>, otherwise not.
>
> It is OK because sizeof(A.vec_) is not dependent on sizeof(A)

Sorry, no, it's not OK. Your point is relevant for most (all?) in-practice
std::vector implementations, but here you're up against formally Undefined
Behavior. And that means that in spite of practical considerations some
implementation might just not work or compile; it might, to ensure that you're
writing Good Code, even check explicitly that type A is complete.


> This is just a tarted up way of saying something like:
>
> struct A
> {
> struct A* vec_;
> };

Sorry, no, that depends on the implementation; see above.


Cheers & hth.,

- Alf

--

Martin B.

unread,
Mar 10, 2010, 10:45:17 AM3/10/10
to

Do you (or someone else) know if an implementation of std::vector that
compiles without errors for an incomplete type could still break at runtime?

cheers,
Martin

Randy

unread,
Mar 10, 2010, 4:48:33 PM3/10/10
to
On Mar 9, 8:19 am, Kris Prad <krisp...@yahoo.co.uk> wrote:

[snip]

> Comeau accepts even this:
>
> #include <vector>
>
> struct A
> {
> std::vector<A> vec_;
> A(): vec_(10) {} // <----- with size
>
> };
>
> BUT NOT THIS:
>
> struct B;
> std::vector<B> vec2_; // incomplete type error
>
> Kris
>

Actually this example appears, IMHO, to be correct based on my reading
of the Standard, 9.2, Class Members, paragraph 2, which states that a
class is considered as a complete type at several points in the class
member specification, explicitly including in ctor initializer lists,
as shown above.

Randy.

Seungbeom Kim

unread,
Mar 11, 2010, 8:14:37 AM3/11/10
to
Randy wrote:
> On Mar 9, 8:19 am, Kris Prad <krisp...@yahoo.co.uk> wrote:
>> Comeau accepts even this:
>>
>> #include <vector>
>>
>> struct A
>> {
>> std::vector<A> vec_;
>> A(): vec_(10) {} // <----- with size
>>
>> };
>>
>> BUT NOT THIS:
>>
>> struct B;
>> std::vector<B> vec2_; // incomplete type error
>
> Actually this example appears, IMHO, to be correct based on my reading
> of the Standard, 9.2, Class Members, paragraph 2, which states that a
> class is considered as a complete type at several points in the class
> member specification, explicitly including in ctor initializer lists,
> as shown above.

A::vec_ would be considered as a complete type in the initializer list
of A::A(), of course. The issue here is (kind of) the other direction,
i.e. whether A would be complete in the instantiation of std::vector<A>
which is the type of A::vec_.

--
Seungbeom Kim

Johannes Schaub (litb)

unread,
Mar 11, 2010, 12:05:15 PM3/11/10
to
Randy wrote:

> On Mar 9, 8:19 am, Kris Prad <krisp...@yahoo.co.uk> wrote:
>
> [snip]
>
>> Comeau accepts even this:
>>
>> #include <vector>
>>
>> struct A
>> {
>> std::vector<A> vec_;
>> A(): vec_(10) {} // <----- with size
>>
>> };
>>
>> BUT NOT THIS:
>>
>> struct B;
>> std::vector<B> vec2_; // incomplete type error
>>
>> Kris
>>
>
> Actually this example appears, IMHO, to be correct based on my reading
> of the Standard, 9.2, Class Members, paragraph 2, which states that a
> class is considered as a complete type at several points in the class
> member specification, explicitly including in ctor initializer lists,
> as shown above.
>

If the semantics require the class type "vector<A>" to be complete, it's
instantiated. A non-static data member definition is such a point: It
requires its type to be complete.

Thus, at "std::vector<A> vec_" an implicit instantiation of the vector type
occurs. The point of instantiation will be prior to "A", so class type "A"
is incomplete within std::vector and thus behavior is undefined.

However, the consideration about what consequences the POI has with regard
to completeness and visibility of subsequent declarations is quite
convoluted - see http://www.open-
std.org/jtc1/sc22/wg21/docs/cwg_active.html#287 ).

In the case of a specialization reference from within a member function body
defined in-class or a ctor-initializer in-class and the other cases where a
class is regarded complete, it seems to me that the current rules do not
clearly state what happens. In other words, currently the following is not
guaranteed to work because the completeness in the context of the ctor-
initializer does not seem to carry over into the instantiated
specialization:

template<typename T> struct B { static int const value = sizeof(T); };

// # POI is here, prior to A.
struct A {
int m;
A():m(B<A>::value) { }
};

The there mentioned rules to add after paragraph 3 seem to be important to
guarantee that this indeed works. In any case, GCC and comeau already accept
this code.

0 new messages