_really_ empty classes

  • Thread starter =?ISO-8859-1?Q?Emmanuel_Thom=E9?=
  • Start date
?

=?ISO-8859-1?Q?Emmanuel_Thom=E9?=

This is a comment aside the empty class behavior FAQ.

I understand there are a fair number of reasons which make empty
classes have non-zero size (except as base classes). If ``class foo''
were of size 0, then:

- an object of type foo may lie at the same address as another.
- arrays of foo objects would have size 0, and pointer arithmetic on
array cell would break.
- requirements of the standard explicitly stating objects have
non-zero size would break (1.7.4 and 5.3.3.2, to start with).

(see also links below).

Now, I claim that even considering this, not having provision for
_really_ empty classes is a fiasco of the standard. The so-called
"empty member c++ optimization" (see links) is not a complete cure: it
relies on the availability of a non-empty member on which empty
classes are aggregated. Such a member is not always available. In
template metaprogramming world, one would prize the ability to create
possibly empty classes made of several possibly all empty members.

Extending the language, say via a keyword, to include ``ghost''
classes (by lack of a better name), which really occupy zero storage
when they are empty and present as members of anything would be, IMHO,
a very valuable addition. Implications would include:

- distinct objects of two non-ghost types would still reside at
distinct addresses. In case one of the object is a ghost class, this
would no longer hold.
- pointer arithmetic on ghost class pointers would be illegal.

Does such a thing make sense to anybody ?

Some relevant links include:

http://www.research.att.com/~bs/bs_faq2.html#sizeof-empty
http://www.cantrip.org/emptyopt.html
http://tinyurl.com/36r26
 
C

Claudio Puviani

Emmanuel Thomé said:
I understand there are a fair number of reasons which make empty
classes have non-zero size (except as base classes). If ``class foo''
were of size 0, then:

- an object of type foo may lie at the same address as another.
- arrays of foo objects would have size 0, and pointer arithmetic on
array cell would break.
- requirements of the standard explicitly stating objects have
non-zero size would break (1.7.4 and 5.3.3.2, to start with).

Correct. I'll add that the higher-level reason for these
requirements/restrictions is so that individual instances can have separate
identities.
Now, I claim that even considering this, not having provision for
_really_ empty classes is a FIASCO [my emphasis] of the standard.

And from logic, we take a long leap into the melodramatic.
The so-called "empty member c++ optimization" (see links) is not a
complete cure:

Before looking for a "cure", shouldn't you perhaps establish that there's a
problem to be solved?
relies on the availability of a non-empty member on which empty
classes are aggregated. Such a member is not always available. In
template metaprogramming world, one would prize the ability to create
possibly empty classes made of several possibly all empty members.

Extending the language, say via a keyword, to include ``ghost''
classes (by lack of a better name), which really occupy zero storage
when they are empty and present as members of anything would be, IMHO,
a very valuable addition.

And after proposing a solution to the yet-to-be stated problem, might it not be
appropriate to describe the benefits of the presumed solution?
Implications would include:

- distinct objects of two non-ghost types would still reside at
distinct addresses. In case one of the object is a ghost class, this
would no longer hold.
- pointer arithmetic on ghost class pointers would be illegal.

And in lieu of benefits, we get a list of pitfalls.
Does such a thing make sense to anybody ?

Only if the goal is to mutilate and pervert the language. Was there a point to
this?

Claudio Puviani
 
C

Clark Cox

Emmanuel Thome said:
This is a comment aside the empty class behavior FAQ.

I understand there are a fair number of reasons which make empty
classes have non-zero size (except as base classes). If ``class foo''
were of size 0, then:

- an object of type foo may lie at the same address as another.
- arrays of foo objects would have size 0, and pointer arithmetic on
array cell would break.
- requirements of the standard explicitly stating objects have
non-zero size would break (1.7.4 and 5.3.3.2, to start with).

(see also links below).

Now, I claim that even considering this, not having provision for
_really_ empty classes is a fiasco of the standard. The so-called
"empty member c++ optimization" (see links) is not a complete cure: it
relies on the availability of a non-empty member on which empty
classes are aggregated. Such a member is not always available. In
template metaprogramming world, one would prize the ability to create
possibly empty classes made of several possibly all empty members.

Extending the language, say via a keyword, to include ``ghost''
classes (by lack of a better name), which really occupy zero storage
when they are empty and present as members of anything would be, IMHO,
a very valuable addition.

Why would it be valuable? What possible benefit could this provide?
 
?

=?ISO-8859-1?Q?Emmanuel_Thom=E9?=

Clark said:
Why would it be valuable? What possible benefit could this provide?

Valuable for efficiency. Benefits for code simplicity, compared to
other (existing) solutions. The explanation below is lengthy, sorry.

As explained in http://www.cantrip.org/emptyopt.html, the nonzero
padding leads to some bloat. For efficiency reasons, ways to avoid it
are desired.

Using a template to make the possibly empty class a base class of some
non-empty member is a way to achieve this. boost::compressed_pair does
so. It is successful in many cases, but restrictions do exist (namely,
you've got to articulate your possibly empty classes around some
non-empty member).

A working code context could be somewhat long and not very
illuminating, I'll only give a sketch (which, unfortunately, does grow
to some length). Consider first the following:

/* objective: behave like an int, without being an int */
template<class T, T v> class fake {
public:
fake() {};
fake(T w) {};
fake(const fake&) {};
~fake() {};
const fake& operator=(const fake&) { return *this;};
T value() { return v; };
};

template<class T, T v> int int_value(fake<T,v> a) { return a.value(); }
int int_value(int a) { return a; }

The idea is to have fake<int, 3> look like an int in some aspects
(really few here :)), but really be 3 always. Attempts to assign
anything else than 3 to it may be tracked, but we do not want to
forbid such assignments altogether.

As it is, an object of type fake<int,3> occupies nonzero storage, but
that doesn't disturb me too much yet. I'm ok with wasting a couple of
bytes on the stack, not too often.

Now say we've got a handful of properties, which may be either
variable (int) or fixed (fake<int, 3>). These properties are put
together in a template:

template<class U, class Ta, class Tb> class qualified_data {
U raw_data;
Ta p1,p2,p3;// some of type Ta
Tb p4,p5,p6;// some of type Tb
/* lots of interface stuff. */
};

of course we want instantiations of qualified_data to be as small as
possible. As written above, bloat will be significant, and arrays
created from qualified_data will be unsatisfyingly large. A solution
could be to use compressed_pair (abbreviated cp), with the following
statement.
cp<Ta,cp<Ta,cp<Ta,cp<Tb,cp<Tb,cp<Tb, U> > > > > > props;
and then, have accessors for the different fields:
const Ta& p1() const { return props.first(); };
...
const Ta& p5() const { return
props.second().second().second().second().first(); };

Even if the above statement can be easily trimmed down by some
classical template tricks, the code obtained is error-prone. The
first, naive syntax above makes life easier. Agreed, this is making
life easier for the developer rather than the user, which is a
debatable objective.

If really empty classes were available, the first, much lighter,
syntax would be just as efficient as the first one.

Now, maybe nobody's convinced. I don't want to bother people with the
innards of the project I'm working on. Furthermore, it does turn out
that restating my original problem this way suggests me adding some
syntactic sugar around the cp<cp<cp<...>>> solution above so that I
can get it to be less error-prone. Maybe it'll work out, I'll see. I'm
thinking of statements such as this:

#define TAG_FIELD_1 0x12340001 // and so on...
field_list <
field<Ta, TAG_FIELD_1,
/* ... */
field<Tb, TAG_FIELD_6,
void> > > > > > > many_fields;
const Ta& p5() const { return many_fields.access(TAG_FIELD_5); };

I honestly believe that this does not discard my argument about the
interest of really empty classes. So much trickery is exhausting.

E.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,008
Latest member
Rahul737

Latest Threads

Top