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

empty base optimization of a multiple common base

4 views
Skip to first unread message

itaj sherman

unread,
May 17, 2010, 2:26:17 PM5/17/10
to
class A {};
class B: public A {};
class C: public A {};
class D: public B, public C {};

which derivations here can accept the empty base optimization by the
standard?
if all of them do, then the two subobjects of type A could have the
same address.

{
D d;
assert( (A*)(B*)&d != (A*)(C*)&d );
}

itaj

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

Vladimir Jovic

unread,
May 18, 2010, 12:03:55 PM5/18/10
to
itaj sherman wrote:
> class A {};
> class B: public A {};
> class C: public A {};
> class D: public B, public C {};
>
> which derivations here can accept the empty base optimization by the
> standard?

B and C structures will have base class optimized. D no, because it has
multiple base classes.

> if all of them do, then the two subobjects of type A could have the
> same address.
>
> {
> D d;
> assert( (A*)(B*)&d != (A*)(C*)&d );
> }
>

Your "example" doesn't compile. See this :
http://www.parashift.com/c++-faq-lite/how-to-post.html#faq-5.8

#include <cassert>


class A {};
class B: public A {};
class C: public A {};
class D: public B, public C {};

int main()
{
D d;
B *b = dynamic_cast< B* > ( &d );
C *c = dynamic_cast< C* > ( &d );
assert( (void*)b != (void*)c );

Maxim Yegorushkin

unread,
May 18, 2010, 5:51:52 PM5/18/10
to
On 17/05/10 19:26, itaj sherman wrote:
> class A {};
> class B: public A {};
> class C: public A {};
> class D: public B, public C {};
>
> which derivations here can accept the empty base optimization by the
> standard?
> if all of them do, then the two subobjects of type A could have the
> same address.
>
> {
> D d;
> assert( (A*)(B*)&d != (A*)(C*)&d );
> }

The standard requires that base class subobjects of the same time have distinct addresses.

In you case, B and C use base class optimization, that is, sizeof(A) == sizeof(B) == sizeof(C).

D, on the other hand, has two base class A subobjects which have to be at different offsets from the beginning of a complete object D, so that sizeof(D) != sizeof(A).

This issue often arises when people use multiple derivation from classes that derive from boost::noncopyable, i.e.:

struct A : boost::noncopyable {};
struct B : boost::noncopyable {};
struct C : A, B {}; // sub-optimal, two boost::noncopyable subobjects

A fix is to make noncopyable a class template:

template<class Derived> class noncopyable
{
noncopyable(noncopyable const&);
noncopyable& operator=(noncopyable const&);
};

struct A : noncopyable<A> {};
struct B : noncopyable<B> {};
struct C : A, B {}; // noncopyable<> optimized away

--
Max

Nikolay Ivchenkov

unread,
May 18, 2010, 10:04:03 PM5/18/10
to
On 19 May, 01:51, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
wrote:

> On 17/05/10 19:26, itaj sherman wrote:
> The standard requires that base class subobjects of the same type have distinct addresses.

Where exactly the definitive text of the current standard contains
such a requirement?

--

Maxim Yegorushkin

unread,
May 19, 2010, 6:25:35 PM5/19/10
to
On 19/05/10 03:04, Nikolay Ivchenkov wrote:
> On 19 May, 01:51, Maxim Yegorushkin<maxim.yegorush...@gmail.com>
> wrote:
>> On 17/05/10 19:26, itaj sherman wrote:
>> The standard requires that base class subobjects of the same type have distinct addresses.
>
> Where exactly the definitive text of the current standard contains
> such a requirement?

I have no idea, try using a pdf reader with a search function.

However, you can derive it from common sense.

The identity of an object in C++ is its address, that is, every unique
object has a unique address. This is a fundamental rule.

If a derived class contains several subobjects of the same base class
these subobjects are distinct from each other and, therefore, they need
to have distinct addresses, even if their class is empty, otherwise the
fundamental identity rule would be violated.

--
Max

Bo Persson

unread,
May 19, 2010, 6:27:49 PM5/19/10
to
Nikolay Ivchenkov wrote:
> On 19 May, 01:51, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
> wrote:
>> On 17/05/10 19:26, itaj sherman wrote:
>> The standard requires that base class subobjects of the same type
>> have distinct addresses.
>
> Where exactly the definitive text of the current standard contains
> such a requirement?

There is a note at the end of section 10 "Derived classes" referring
to pointer comparisons in section 5.10 - two pointers to objects of
the same type compares equal if they point to the same object.
Therefore, two different objects of the same type must have different
addresses.


Bo Persson

Nikolay Ivchenkov

unread,
May 20, 2010, 11:33:35 PM5/20/10
to
On 20 May, 02:25, Maxim Yegorushkin <maxim.yegorush...@gmail.com>

wrote:
> On 19/05/10 03:04, Nikolay Ivchenkov wrote:
> > On 19 May, 01:51, Maxim Yegorushkin<maxim.yegorush...@gmail.com>
> > wrote:
> > > The standard requires that base class subobjects of the same type have distinct addresses.

> > Where exactly the definitive text of the current standard contains
> > such a requirement?

> I have no idea, try using a pdf reader with a search function.

That was a rhetorical question, because I believe that C++03 does not
require what you said above.

According to C++98 - 5.10/1,

"Two pointers of the same type compare equal if and only if they are
both null, both point to the same object or function, or both point
one past the end of the same array."

In C++03 this wording was changed to

"Two pointers of the same type compare equal if and only if they are
both null, both point to the same function, or both represent the same
address (3.9.2)."

See defect report #73 for details:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3084.html#73

Lets consider C++98/C++03 - 10/5 now:

"[Note: [...] two subobjects that have the same class type and that
belong to the same most derived object must not be allocated at the
same address (5.10). ]"

This non-normative wording refers to 5.10. Though it was relevant to C+
+98 - 5.10, this note isn't correct in C++03 since the wording of 5.10
was changed as shown above.

> However, you can derive it from common sense.
>
> The identity of an object in C++ is its address, that is, every unique
> object has a unique address. This is a fundamental rule.

First, it is not stated in C++03. The resolution for DR 734 fixes this
hole:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3084.html#734

Second, it's very funny that the committee makes the same ridiculous
mistake as you do: no exception is made for objects and their
subobjects. Lets consider the following:

int arr[1];

Here arr and its element arr[0] are two distinct objects: the former
one has the type "array of 1 int", the latter one has the type "int".
According to N3092 - 1.8/6, these objects shall have distinct
addresses:

"Unless an object is a bit-field or a base class subobject of zero
size, the address of that object is the address of the first byte it
occupies. Two distinct objects that are neither bit-fields nor base
class subobjects of zero size shall have distinct addresses."

> If a derived class contains several subobjects of the same base class
> these subobjects are distinct from each other and, therefore, they need
> to have distinct addresses, even if their class is empty, otherwise the
> fundamental identity rule would be violated.

This isn't stated in C++03/C++0x, and this doesn't work in practice:

#include <iostream>

struct B
{
B() { std::cout << this << std::endl; }
};

struct D : B
{
B b;
};

int main()
{
D d;
}

VC++ 9.0 and Intel C++ 11.0.061 for Windows place two D's subobjects
of type B at the same address.

--

Maxim Yegorushkin

unread,
May 21, 2010, 8:34:13 AM5/21/10
to
On 21/05/10 04:33, Nikolay Ivchenkov wrote:
> On 20 May, 02:25, Maxim Yegorushkin<maxim.yegorush...@gmail.com>
> wrote:
>> On 19/05/10 03:04, Nikolay Ivchenkov wrote:
>>> On 19 May, 01:51, Maxim Yegorushkin<maxim.yegorush...@gmail.com>
>>> wrote:
>>>> The standard requires that base class subobjects of the same type have distinct addresses.
>
>>> Where exactly the definitive text of the current standard contains
>>> such a requirement?
>
>> I have no idea, try using a pdf reader with a search function.
>
> That was a rhetorical question, because I believe that C++03 does not
> require what you said above.
>
> According to C++98 - 5.10/1,
>
> "Two pointers of the same type compare equal if and only if they are
> both null, both point to the same object or function, or both point
> one past the end of the same array."
>
> In C++03 this wording was changed to
>
> "Two pointers of the same type compare equal if and only if they are
> both null, both point to the same function, or both represent the same
> address (3.9.2)."

Basically, they are preparing to say that empty base class subobjects
can be optimized away.

> See defect report #73 for details:
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3084.html#73
>
> Lets consider C++98/C++03 - 10/5 now:
>
> "[Note: [...] two subobjects that have the same class type and that
> belong to the same most derived object must not be allocated at the
> same address (5.10). ]"
>
> This non-normative wording refers to 5.10. Though it was relevant to C+
> +98 - 5.10, this note isn't correct in C++03 since the wording of 5.10
> was changed as shown above.
>
>> However, you can derive it from common sense.
>>
>> The identity of an object in C++ is its address, that is, every unique
>> object has a unique address. This is a fundamental rule.
>
> First, it is not stated in C++03. The resolution for DR 734 fixes this
> hole:
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3084.html#734
>
> Second, it's very funny that the committee makes the same ridiculous
> mistake as you do: no exception is made for objects and their
> subobjects. Lets consider the following:
>
> int arr[1];
>
> Here arr and its element arr[0] are two distinct objects: the former
> one has the type "array of 1 int", the latter one has the type "int".
> According to N3092 - 1.8/6, these objects shall have distinct
> addresses:

Not sure who is making what mistake, probably the wording is just less
than perfect.

> "Unless an object is a bit-field or a base class subobject of zero
> size, the address of that object is the address of the first byte it
> occupies. Two distinct objects that are neither bit-fields nor base
> class subobjects of zero size shall have distinct addresses."

Looks like they now explicitly allow optimizing away empty base classes
of the same type in C++03. Please note that the above does not require
two empty base subobjects of the same type to have the same address.

There is an important footnote in the link you provided:

[Footnote: Under the “as-if” rule an implementation is allowed to store
two objects at the same machine address or not store an object at all if
the program cannot observe the difference (1.9 [intro.execution]). —end
footnote].

Note the "if the program cannot observe the difference". Don't know if
this footnote made it into the standard.

>> If a derived class contains several subobjects of the same base class
>> these subobjects are distinct from each other and, therefore, they need
>> to have distinct addresses, even if their class is empty, otherwise the
>> fundamental identity rule would be violated.
>
> This isn't stated in C++03/C++0x, and this doesn't work in practice:

My practise is different.

> #include<iostream>
>
> struct B
> {
> B() { std::cout<< this<< std::endl; }
> };
>
> struct D : B
> {
> B b;
> };
>
> int main()
> {
> D d;
> }
>
> VC++ 9.0 and Intel C++ 11.0.061 for Windows place two D's subobjects
> of type B at the same address.

$ g++ --version
g++ (GCC) 4.4.1 20090725 (Red Hat 4.4.1-2)
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ g++ -O3 -Wall -o test test.cc
$ ./test
0x7fffc2d173f0
0x7fffc2d173f1

--
Max

Nikolay Ivchenkov

unread,
May 22, 2010, 3:06:47 PM5/22/10
to
On 20 May, 02:27, "Bo Persson" <b...@gmb.dk> wrote:
> Nikolay Ivchenkov wrote:
> > On 19 May, 01:51, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
> > wrote:
> > > On 17/05/10 19:26, itaj sherman wrote:
> > > The standard requires that base class subobjects of the same type
> > > have distinct addresses.
>
> > Where exactly the definitive text of the current standard contains
> > such a requirement?
>
> There is a note at the end of section 10 "Derived classes" referring
> to pointer comparisons in section 5.10

This is not definitive part of the standard. See ISO/IEC Directives,
Part 2 (Rules for the structure and drafting of International
Standards) - 6.5.1:

"Notes and examples integrated in the text of a document shall only be
used for giving additional information intended to assist the
understanding or use of the document. These elements shall not contain
requirements or any information considered indispensable for the use
of the document."

On 21 May, 16:34, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
wrote:


> On 21/05/10 04:33, Nikolay Ivchenkov wrote:
>
> > Second, it's very funny that the committee makes the same ridiculous
> > mistake as you do: no exception is made for objects and their
> > subobjects. Lets consider the following:
>
> > int arr[1];
>
> > Here arr and its element arr[0] are two distinct objects: the former
> > one has the type "array of 1 int", the latter one has the type "int".
> > According to N3092 - 1.8/6, these objects shall have distinct
> > addresses:

In addition, objects can share a common region of storage as shown
below:

#include <iostream>
#include <string>

struct X
{
unsigned char buf[sizeof(std::string)];
};

int main()
{
X *p = new X;
new(p) std::string("text");

std::cout << *(std::string *)(void *)p->buf << std::endl;

((std::string *)(void *)p)->~basic_string<char>();
delete p;
}

Here the object of type std::string has the same address as:
- the object of type X,
- the array, and
- the first element of the array.

> probably the wording is just less than perfect

These wordings are much less than perfect.

> > "Unless an object is a bit-field or a base class subobject of zero
> > size, the address of that object is the address of the first byte it
> > occupies. Two distinct objects that are neither bit-fields nor base
> > class subobjects of zero size shall have distinct addresses."
>

> Please note that the above does not require
> two empty base subobjects of the same type to have the same address.

Sure.

> There is an important footnote in the link you provided:
>

> [Footnote: Under the �as-if� rule an implementation is allowed to store


> two objects at the same machine address or not store an object at all if

> the program cannot observe the difference (1.9 [intro.execution]). �end
> footnote].

This is just a consequence of the rules described in the subclause
1.9. Strictly speaking, objects of the abstract machine (that are
discussed in this thread) and objects of a real execution environment
are different things.

> > > If a derived class contains several subobjects of the same base class
> > > these subobjects are distinct from each other and, therefore, they need
> > > to have distinct addresses, even if their class is empty, otherwise the
> > > fundamental identity rule would be violated.
>
> > This isn't stated in C++03/C++0x, and this doesn't work in practice:
>
> My practise is different.

I know how GNU C++ behaves and I'm very happy for you. But your "rule"
is not reliable in general.


--

Nikolay Ivchenkov

unread,
May 22, 2010, 3:07:31 PM5/22/10
to
Nikolay Ivchenkov wrote:
>
> In addition, objects can share a common region of storage as shown below

Moreover, an object whose type doesn't have a non-trivial constructor
can exist even if it is not explicitly created (see 3.8/1).

#include <iostream>
#include <new>

int main()
{
unsigned char *p =
new unsigned char[sizeof(int)];

*(short *)(void *)p = 11;
std::cout << *(short *)(void *)p << std::endl;

new(p) int(22);
std::cout << *(int *)(void *)p << std::endl;

delete[] p;
}

Here the lifetime of the object of type "short" located at the address
pointed to by p begins when a storage with the proper alignment and
size is obtained by the former new-expression.

--

Bo Persson

unread,
May 23, 2010, 2:43:26 PM5/23/10
to
Nikolay Ivchenkov wrote:
> On 20 May, 02:27, "Bo Persson" <b...@gmb.dk> wrote:
>> Nikolay Ivchenkov wrote:
>>> On 19 May, 01:51, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
>>> wrote:
>>>> On 17/05/10 19:26, itaj sherman wrote:
>>>> The standard requires that base class subobjects of the same type
>>>> have distinct addresses.
>>
>>> Where exactly the definitive text of the current standard contains
>>> such a requirement?
>>
>> There is a note at the end of section 10 "Derived classes"
>> referring
>> to pointer comparisons in section 5.10
>
> This is not definitive part of the standard. See ISO/IEC Directives,
> Part 2 (Rules for the structure and drafting of International
> Standards) - 6.5.1:
>
> "Notes and examples integrated in the text of a document shall only
> be used for giving additional information intended to assist the
> understanding or use of the document. These elements shall not
> contain requirements or any information considered indispensable
> for the use of the document."

I know, but section 5.10 is normative text. The note just points us
there.


>
> In addition, objects can share a common region of storage as shown
> below:
>
> #include <iostream>
> #include <string>
>
> struct X
> {
> unsigned char buf[sizeof(std::string)];
> };
>
> int main()
> {
> X *p = new X;
> new(p) std::string("text");
>
> std::cout << *(std::string *)(void *)p->buf << std::endl;
>
> ((std::string *)(void *)p)->~basic_string<char>();
> delete p;
> }
>
> Here the object of type std::string has the same address as:
> - the object of type X,
> - the array, and
> - the first element of the array.
>

Two elements of *different* types can have the same address, like a
POD struct and its first member.

Objects of the same type cannot. If two pointers of the same type
compares equal, they either point to the same object, or no objects at
all (like the position after the end of an array).

They can not point to two different empty base classes of the same
type, and still compare equal. Therefore the base classes must have
different addresses.


Bo Persson

--

itaj sherman

unread,
May 23, 2010, 2:47:41 PM5/23/10
to

Let me get this straight, you're saying that c++98 was not clear about
this, and it has been decided to fix for c++03 that pointers to two
such subobjects may compare equal, allowing the empty base
optimization to be used freely in that case.
Which means I cannot compare such pointers to an empty class, and know
whether they are the same subobject or not.

If so, I need to make sure: What if the class is not empty, but the
compiler can prove that the 2 subobjects always have the same state,
is it then allowed to optimize and have them at the same address?
for example:

class A
{
virtual ~A();
};

class B: public A {};
class C: public A {};
class D: public B, public C {};

{
D d;
assert( (A*)(B*)&d != (A*)(C*)&d ); //does c++03 promise this
assertion if A is not empty?
}

or

class A
{
int const m;

public: A()
:
m(0)
{}
};

itaj sherman

unread,
May 24, 2010, 11:47:26 AM5/24/10
to

>From section 10:

5 [Note: A base class subobject might have a layout (3.7) different
from the layout of a most derived object of
the same type. A base class subobject might have a polymorphic
behavior (12.7) different from the polymorphic
behavior of a most derived object of the same type. A base class
subobject may be of zero size
(clause 9); however, two subobjects that have the same class type and


that belong to the same most derived
object must not be allocated at the same address (5.10). ]

That means that in my original example the two base subobjects of type
A must have different addresses, because they have the same mode
derived type D. Do compilers actually comply with this?

Nikolay's example is different. It's a subobject of base class and a
data member. They are of the same type, but not of the same most
derived object. So this section of the standard doesn't apply.

itaj

Nikolay Ivchenkov

unread,
May 25, 2010, 12:33:56 PM5/25/10
to
On 23 May, 22:47, itaj sherman <itajsher...@gmail.com> wrote:
>
> Let me get this straight, you're saying that c++98 was not clear about
> this, and it has been decided to fix for c++03 that pointers to two
> such subobjects may compare equal.

No, I didn't say that.

> Which means I cannot compare such pointers to an empty class, and know
> whether they are the same subobject or not.

The current standard does not explicitly require two objects of the
same type to have distinct addresses.

> If so, I need to make sure: What if the class is not empty, but the
> compiler can prove that the 2 subobjects always have the same state,
> is it then allowed to optimize and have them at the same address?
> for example:

I would like to consider a more simple case:

#include <cassert>

int main()
{
signed char const x[2] = {};
unsigned char const y[2] = {};
assert((void *)&x[0] != &y[0]); // may fail
assert((void *)&x[0] != &y[1]); // may fail
assert((void *)&x[1] != &y[0]); // may fail
}

I think, here x and y may overlap in any way (this is not explicitly
prohibited by the C++03 rules).

On 23 May, 22:43, "Bo Persson" <b...@gmb.dk> wrote:
>
> If two pointers of the same type
> compares equal, they either point to the same object, or no objects at
> all (like the position after the end of an array).

ISO/IEC 14882:2003 - 5.10 does not require that. ISO/IEC 14882:1998 is
obsolete - see C++03 - Foreword:

"This second edition cancels and replaces the first edition (ISO/IEC
14882:1998), which has been technically revised."

On 24 May, 19:47, itaj sherman <itajsher...@gmail.com> wrote:
>
> 5 [Note: A base class subobject might have a layout (3.7) different
> from the layout of a most derived object of
> the same type. A base class subobject might have a polymorphic
> behavior (12.7) different from the polymorphic
> behavior of a most derived object of the same type. A base class
> subobject may be of zero size

> (clause 9); however, two subobjects that have the same class type and


> that belong to the same most derived
> object must not be allocated at the same address (5.10). ]
>

> That means that in my original example the two base subobjects of type
> A must have different addresses, because they have the same mode
> derived type D.

Notes are not normative and they can be completely ignored by
implementors.

> Do compilers actually comply with this?

If you mean all existing C++ compilers, you can't get a reliable
affirmative answer for that question.

> Nikolay's example is different. It's a subobject of base class and a
> data member. They are of the same type, but not of the same most
> derived object.

Do you want to say that here

#include <iostream>

struct B
{
B() { std::cout << this << std::endl; }
};

struct D : B
{
B b;
};

int main()
{
D d;
}

b is not a subobject of d? The note refers to "two subobjects", not to
"two base subobjects" only, so member subobjects are also considered.

itaj sherman

unread,
May 25, 2010, 2:56:37 PM5/25/10
to
On May 25, 7:33 pm, Nikolay Ivchenkov <ts...@mail.ru> wrote:
> On 23 May, 22:47, itaj sherman <itajsher...@gmail.com> wrote:
>
>
>
> > Let me get this straight, you're saying that c++98 was not clear about
> > this, and it has been decided to fix for c++03 that pointers to two
> > such subobjects may compare equal.
>
> No, I didn't say that.
>
> > Which means I cannot compare such pointers to an empty class, and know
> > whether they are the same subobject or not.
>
> The current standard does not explicitly require two objects of the
> same type to have distinct addresses.
>

And it seems that c++0X does generally, but not in my original case of
two empty subobject base objects of the same most derived object (nor
bit field case). right?

> > If so, I need to make sure: What if the class is not empty, but the
> > compiler can prove that the 2 subobjects always have the same state,
> > is it then allowed to optimize and have them at the same address?
> > for example:
>
> I would like to consider a more simple case:
>
> #include <cassert>
>
> int main()
> {
> signed char const x[2] = {};
> unsigned char const y[2] = {};
> assert((void *)&x[0] != &y[0]); // may fail
> assert((void *)&x[0] != &y[1]); // may fail
> assert((void *)&x[1] != &y[0]); // may fail
> }
>
> I think, here x and y may overlap in any way (this is not explicitly
> prohibited by the C++03 rules).
>

And did you mean before that it is required by c++0X resolution for DR
734?

I meant standard complying ones... lol.
I probabely meant the popular ones.
As in, I need to know how safe it is to assume this rule into my code,
and how badly I need to document that if I do. I could also think of
adding some assetions maybe so that if the code is ever compiled on a
different compiler it will verify this (for the used classes).

> > Nikolay's example is different. It's a subobject of base class and a
> > data member. They are of the same type, but not of the same most
> > derived object.
>
> Do you want to say that here
>
> #include <iostream>
>
> struct B
> {
> B() { std::cout << this << std::endl; }
> };
>
> struct D : B
> {
> B b;
> };
>
> int main()
> {
> D d;
> }
>
> b is not a subobject of d? The note refers to "two subobjects", not to
> "two base subobjects" only, so member subobjects are also considered.
>

- 5 ... however, two subobjects that have the same class type and that


belong to the same most derived object must not be allocated at the
same address (5.10). ]

Ok, now that's a really unclear wording of the standard (IMO).

I'll try be clear:
There's the defined instance D d; I'll call the b data member object
x. The base object of type B I'll call y.
Both x and y are of type B.
The most derived object of x is itself, that is x, which is the b data
member.
The most derived object of y is d.
The conclusion must be that the most derived object of x and the most
derived object of y are not the same object.

Now comes the question whether that note should apply to x and y or
not:

They both have type B.
But what does the rest mean?
I really thought this what "belong" means here, that is: belong to the
same most derived object == they have the same most derived object.
And their most derived objects are not the same one (as I cleared
above), which is why I said that doesn't apply to them.
Now that you inquired, I'm not sure, does b being a data member of d
means that it "belongs" to it in the way this note refers to?

itaj

0 new messages