🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

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

Re: class.union questions

#3252
From: James Kanze US/ESC 60/3/141 #407...
Date: Tue, 06 Feb 1996 02:35
200 lines
9376 bytes
In article <v01530500ad38ee830c96@[194.163.74.11]>
dirk@becker.adviser.com (Dirk Becker) writes:

|> In Section 9.6.1 Unions [class.union] there is a large collection of
|> restrictions on the union itself and on candidate member classes.
|> I would like to ask for reasons of several of them:

One of the principle goals was that a union be compatible with a C
union, at least in use.  One of the rules is that the addresses of all
of the elements in the union compare equal to the address of the union
itself, when cast to the appropriate type.

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

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

|> This prevents any construction like the following, where one would
|> like to extend the union contained in some base class:

|> class GenericInterpreter {
|>     union opcode {
|>         short tag;
|>         struct { short tag; short data; } op1;
|>         struct { short tag; char* data; } op2;
|>     };
|> };

|> class SpecialInterpreter : public GenericInterpreter {
|>     union opcode : public GenericInterpreter::opcode {
|>         struct { short tag; long  data; } op3;
|>         struct { short tag; float data; } op4;
|>     };
|> };

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

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

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

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

A user declared constructor, destructor or assignment operator is by
definition never trivial.

|> The quoted WP sequence tries its best to exclude virtual function
|> tables from the union members, which is just fine. But through the
|> non-trivialness you also exclude _any_ custom make of the copy
|> constructor and assignment operator.
|> So you can have any special assignment operator or constructor for
|> your member objects, any but the most common and useful ones.

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

|> Another sample:

|> class longlong { ... };
|> struct opcode {
|>     short tag;
|>     union {
|>         short     op1;
|>         long      op2;
|>         longlong  op3;
|>     } data;
|> };

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

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

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

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

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

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

In practice, I don't think I've used a union in my code since
switching to C++, except in memory management code (ensuring
alignment, etc.).

|> Let's combine this with the problems of a) and b):

|> class GenericInterpreter {
|>     union opcode {
|>         opcode(const opcode&);               // replace implicitly defined
|>         opcode& operator = (const opcode&);  // memberwise copy versions

|>         short tag;
|>         struct { short tag; short data; } op1;
|>         struct { short tag; char* data; } op2;
|>     };
|> };

|> class SpecialInterpreter : public GenericInterpreter {
|>     union opcode {
|>         GenericInterpreter::opcode generic; // error: non-trivial member
|>         short tag;
|>         struct { short tag; long  data; } op3;
|>         struct { short tag; float data; } op4;
|>     };
|> };

|> class ValidInterpreter : public GenericInterpreter {
|>     union opcode {
|>         // using copy - paste instead of language features
|>         short tag;
|>         struct { short tag; short data; } op1;
|>         struct { short tag; char* data; } op2;
|>         //
|>         struct { short tag; long  data; } op3;
|>         struct { short tag; float data; } op4;
|>     };
|> };

|> In my opinion, comparing the copy constructor/assignments to others, they
|> are only special regarding their use by the implicitly defined memberwise
|> copy constructor/assignment by outside classes. Here I can't find a reason
|> why a member's custom copy methods should not be applicable instead.

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

There was some discussion concerning this.  There were two points of
view: one supported basically what you are proposing, the other the
current situation.  The current situation won.

I argued the current situation on the grounds that the above is just
too dangerous.  Basically, it makes it too simple to access
unconstructed objects of types which require a constructor.  Most of
the uses I see for a union are at the low level, where C like code is
perfectly appropriate.

IMHO, things like your example are much better handled with derived
classes (in which the vptr replaces the tag for type information).
Even in cases where the union might be more appropriate, what would be
needed is a discriminated union, which is a completely different beast
than the union we now have.  (I've yet to see what I would consider a
convincing example of the advantage of a discriminated union over
polymorphism, but I know that others disagree with me here.)

|> d) anonymous union

|> Why is there no anonymous struct? This is not only for symmetric reasons,
|> but anonymous structs inside unions may be as useful as anonymous unions
|> inside structs.

Agreed here.  Although I really don't see it as worth the bother.  (I
could do without anonymous unions, too.)
--
James Kanze         Tel.: (+33) 88 14 49 00        email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs-Bourgeois, F-67000 Strasbourg, France
Conseils, itudes et rialisations en logiciel orienti objet --
                -- A la recherche d'une activiti dans une region francophone
---
[ 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://dogbert.lbl.gov/~matt/std-c++/policy.html. ]
Technical Details
Message-ID: <KANZE.96Feb5132431@slsvewt.lts.sel.alcatel.de>
Path: arc1-ca.pugleaf.net!in1.uu.net!bounce-back
References: <v01530500ad38ee830c96@[194.163.74.11]>

Show Full Headers
Complete Article Headers
From: kanze@lts.sel.alcatel.de (James Kanze US/ESC 60/3/141 #40763)
Message-ID: <KANZE.96Feb5132431@slsvewt.lts.sel.alcatel.de>
X-Original-Date: 05 Feb 1996 12:24:28 GMT
Path: arc1-ca.pugleaf.net!in1.uu.net!bounce-back
Date: 06 Feb 96 02:35:51 GMT
Approved: fjh@cs.mu.oz.au
Newsgroups: comp.std.c++
Subject: Re: class.union questions
In-Reply-To: dirk@becker.adviser.com's message of 03 Feb 96 17:22:11 GMT
Organization: SEL
References: <v01530500ad38ee830c96@[194.163.74.11]>
Apparently-To: std-c++@ncar.ucar.edu
X-Auth: PGPMoose V1.1 PGP comp.std.c++
 iQBFAgUBMRa+ouEDnX0m9pzZAQE6CAF9ENug9/g7C1pecakAJvzfFetiirJEg3Uv
 TwmlbBbghQgvuIAfGBInIk2kxgowu6Aj
 =yvyy