🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.std.c++
Article #3341

Re: class.union questions

#3341
From: Dirk Becker
Date: Sat, 10 Feb 1996 15:39
306 lines
12128 bytes
Sorry to be late with this, but I just learned my newsserver
does not yet deliver my posts.

The first replies to my (DB) original posting seem to have the same
objection against my interpretation of b). This is my major problem,
so I put it first now.

James Kanze (JK) and John Max Skaller (JS) wrote:

From the WP: [class.union]

b) "An object of a class with a non-trivial default constructor
     (_class.ctor_), a non-trivial copy constructor (_class.copy_),
     a non-trivial destructor (_class.dtor_), or a non-trivial copy
     assignment operator (_over.ass_, _class.copy_) cannot be a member
     of a union,"

DB> This is a major reduction of the restrictions found in ARM, where all
DB> kinds of constructors or assignment operators were excluded. Here is
DB> also a note to give the explanation by the assumption, any member
DB> functions and especially assignments would usually expect a correctly
DB> constructed object.

JS>       No. It is merely a more precise statement of the same
JS> restrictions using more technical terminology.

JK> This is, in fact, exactly the restriction in the ARM.  All that has
JK> changes is the way things are described.  In the ARM, classes may or
JK> may not have constructors (destructors, assignment operators). In the
JK> current standard, *EVERY* class/struct has a copy constructor, an
JK> assignment operator and a destructor; the distinction is that some of
JK> these are trivial.

I still disagree totally. Please tell me what I'm reading wrong.

The ARM (mine is an early edition, 1990) does a complete restriction
against *any* constructor, destructor or user-defined assignment for
union member objects. (Section 9.5, page 182). The only explanation for
this rule was already summarized in my original posting, quoted above;
it has nothing to do with bitwise copy.

The WP indeed allows member objects with a rich set of constructors
and assignment operators. It does only <g> cripple the 4 most commonly
used special member functions, exactly naming them, into trivialness.
- default constructor
- destructor
- copy constructor
- copy assignment
I assume this exact enumeration implies all other variants of constructor
and assignment in member objects are free for user defined versions.
So the following sample seems perfectly legal with the WP, not with ARM.
The commented lines are the trivialness-required special member functions.

class Bar {
public:
//  Bar();                          // trivial default constructor
//  Bar(const Bar&);                // trivial copy constructor
//  ~Bar();                         // trivial destructor
//  Bar& operator = (const Bar&);   // trivial copy assignment

    // plain member function - no problem
    Bar& operator_assign(const Bar&);

    // some other constructors - I see no problem here
    Bar(int);
    Bar(float);

    // several other simple assignments are allowed
    Bar& operator = (int);
    Bar& operator = (float);

    // these are assignments too, see ARM 5.17 or [expr.ass]
    Bar& operator += (const Bar&);
    Bar& operator -= (const Bar&);
    Bar& operator *= (const Bar&);
    Bar& operator /= (const Bar&);

    // ... data members etc.
};

union Foo {
    Bar  bar;
    // ... others
};

DB> So you can have any special assignment operator or constructor for
DB> your member objects, any but the most common and useful ones.

JK> Correct. The reason is simple: assignment, copy, default construction
JK> and destruction of a union must be correctly defined without the
JK> compiler knowing which element the union actually contains. The set
JK> of forbidden operators are those that the compiler might be called
JK> upon to use in implicitly generated code.

But why would you like to provide your own constructor and assignments?
Maybe because you have added some side effects, like range checking or
trigger a reference counter.
If you only use arguments matching your custom routines, this will work
fine. But if you accidentally assign one Foo.bar to another, your
side-effects won't take place because the WP prevented your custom copy
assignment operator.
It is even impossible to hide the unwanted implicit constructors and
copy assignment into privacy, to make your compiler produce an error
message.

Foo     foo1;
Foo     foo2;

foo1.bar = 4711;        // applies your side-effects
foo2.bar = foo1.bar;    // just trivial assignment here due to WP

-----

a)  "A union shall not have base classes. A union shall not be used as
      a base class."

JK> Without this rule, you either break the address rule, or you impose
JK> some special class layout on unions.

The union has no special class layout? I would say it is very special.
"a class whose member objects all begin at offset zero"

-----

/* my sample of union inheritance */

JK> This is generally handled better by an abstract base class and
JK> virtual functions.

Sure, I like polymorphic objects. But if you have a vast amount of
really small objects you may get tempted to use the reduced footprint
of a (1 or 2 byte) tag instead of a sizeof(ptr) vptr.
It is also slightly faster to load these unions from a binary file
than from a stream, allocate, dispatch and construct every single
object and retrieve every single member.

-----

/* union with longlong as member */

DB> If you are lucky to have some native long long datatype, then this
DB> would be legal code. If you already had to implement your own
DB> class longlong, now you lost the chance to use it (here).

JK> This is a problem, but... how would the compiler know when to call
JK> the constructor/destructor of your longlong?

Sorry for the wrong sample, longlong may work with trivial c/dtor and
copy operations. The same is with some class Complex.

Let's reconsider a class "string" and reference-counter features as
sideeffects. The answer for your question - how would the compiler
know? - came with my conclusion as quoted below: Just tell him.
Here the union constructor itself gets responsible for construction as
the correct union variant - e.g. choose the correct discriminator tag,
like "empty".

DB> I would generally prefer " ... constructors and destructor of member
DB> objects are ignored, if not explicitly called by the union's ctor/dtor.
DB> The union's implicit default copy constructor / assignment does a
DB> binary copy instead of memberwise copy. Objects <containing a vptr>
DB> cannot be member of a union" - Please pardon my shortcut on vptrs.

The missing sample for this:

class Member {
public:
  Member();
  // ...
};
class Other {
public:
  Other();
  // ...
};

union Union {
  Union();
  Union(int);
  // ...
  Member m;
  Other  o;
};

Union::Union()
{ // call none of the base member constructors
}

// call Member::Member() using a wellknown notation
Union::Union(int a)
: m()
{ }

// deluxe variant - but this would mean a severe extension
Union::Union(int tag)
: tag==1 ? m()  // call Member::Member()
: tag==2 ? o()  // otherwise call Other::Other()
{ }

-----

c) "A union can be thought of as a class"

DB> Here you accept the implications of 12.8.8 and 12.8.13 on implicitly
DB> defined copy constructor and copy assignment. The already trivial
DB> copy constructor/assignment of the member objects will then result
DB> in one large repetition of copies from and to the same memory locations.
DB> To avoid this behaviour I would strongly recommend to implement your
DB> own copy constructor or assignment whenever you use a union, because a
DB> memberwise copy is usually not desired.

JK> Basically, if you study the restrictions on unions carefully, the
JK> rules are such that a bitwise copy *must* be legal for assignment and
JK> copy construction.  This is the only type of copy the compiler can
JK> cope with in the absense of a discriminator to tell it which element
JK> of the union is valid.

JK> IMHO: if you feel that your union needs a copy constructor or an
JK> assignment operator, you are probably doing something wrong, in the
JK> sense that you are trying to use unions in a way that they are not
JK> intended.  (As they are defined in the standard. If they were defined
JK> differently, they might have different reasonable uses.)

I knew, but some compiler implementors forget their homework here.
If for any reason (generated source) you have a union with lots of
members, you may accidentally get lots of redundant code. This is
another reason for implicit bitwise copy.

And if the trivialness requirement were removed from the copy
operations, like in my (very draft) replacement text quoted above,
you would also loose the equivalence.

From a discussion outside this newsgroup I finally got a hint on the
intention behind the new restrictions: They should ensure a bitwise
copy of the union will copy the current union member correctly.
This is achieved by forcing bitwise copy for any potential member.

Why did you then not stay just with the POD union? With the
differentiation between POD union and non-POD union you already
got the higher degree of complexity, only to undo this with lots
of additional words.

My first incident with this problem went what I would call a
user's way instead of the implementor's sight: How can we use
it? Instead of "How to infer the implicit implementation?"
Here I felt invited by the WP to use most of the member object
assignments and than fell into the trivialness trap.

If your intention is bitwise copy as implicit copy for the union
itself, why not just call it by its real name instead of hiding
behind "trivialness"?
"The implicit union copy constructor and copy assignment apply
bitwise copy."
No trivialness restrictions on the member objects!
If someone then decides to have member objects with other copy
constructor or assignment semantics, he/she just has to provide
own union copy operations.

-----
d)

DB> Why is there no anonymous struct?

JS> The committee (as a body) does not find arguments for
JS> orthogonal design appealing. It is hard to demonstrate
JS> why orthogonal design is good to those who have little
JS> experience with orthogonal systems -- most of us,
JS> considering the horrid systems we're forced to work with
JS> most of the time.

JK> Agreed here. Although I really don't see it as worth the bother.
JK> (I could do without anonymous unions, too.)

You won't need the anonymous struct, because you already said you
don't use unions.

-----

Some new questions:

e) "At most one of the member objects can be stored in a union at
    any time."

Has anybody already thought about the memberwise implicit operations in
regard to this rule?
OK, we'll meet again at the bitwise copy, but this is at least nowhere
explicitly mentioned in the WP.

-----

f)

Why is [class.mem] 15 restricted to POD structs as union members?
vptrs are already excluded for union members, and I would still
ask to exclude them when dropping the trivialness.

------------------------------------------------------------------
Dirk Becker                                dirk@becker.adviser.com
Harderweg 76, 22549 Hamburg, Germany          Tel. +49 40 80783143



[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy is
  summarized in http://reality.sgi.com/employees/austern_mti/std-c++/policy.html
]
Technical Details
Message-ID: <v01530501ad3e247b1270@[194.163.74.11]>
Path: arc1-ca.pugleaf.net!engnews1.Eng.Sun.COM!taumet!clamage

Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!engnews1.Eng.Sun.COM!taumet!clamage
From: dirk@becker.adviser.com (Dirk Becker)
Newsgroups: comp.std.c++
Subject: Re: class.union questions
Date: 10 Feb 1996 15:39:23 GMT
Organization: ?
Approved: clamage@eng.sun.com (comp.std.c++)
Message-ID: <v01530501ad3e247b1270@[194.163.74.11]>
NNTP-Posting-Host: taumet.eng.sun.com
Mime-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
X-Sender: dirk@194.163.74.12
Cc: kanze@lts.sel.alcatel.de
Content-Length: 11074
Originator: clamage@taumet