From: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Subject: Variants
Organization: MAXTAL P/L C/- University Computing Centre, Sydney
Date: Fri, 1 Jan 1993 17:41:33 GMT

I am considering the problem of heterogenous aggregates,
in which for example we have an array of pointers to objects
that may be either a V1, V2 or V3 object. These objects need
not share a common base, and even if they did, we do not
wish to use dynamic downcasting for this purpose.

Furthermore, the classes for V1, V2 and V3 may already be
closed and not available for modification, and they
may or may not contain virtual functions.

The 'correct' solution is basically as follows:

	struct V {
		enum {t1,t2,t3} tag;
		union {
			V1 * v1;
			V2 * v2;
			V3 * v3;
		};
		V(V1* x1) : tag(t1), v1(x1) {}
		V(V2* x2) : tag(t2), v2(x2) {}
		V(V3* x3) : tag(t3), v3(x3) {}
	} v;

	switch(v.tag) {
		case t1: ... v.v1 ... break; 
		case t2: ... v.v2 ... break;
		case t3: ... v.v3 ... break;
	};

This is better than using a void* and a cast I think, because it constrains
the user to accessing one of the declared types V1, V2 or V3.

However, it is clearly insecure in that the tag need not agree with the
actual type pointed to, and the switch might have the same problem
(to say nothing of forgetting the break).

To fix this problem I invent a secure form of the union called a variant,
its not quite as above but that is the initial picture.

	variant V [V1*, V2*, V3*] v;
	select(v)
	{
		type(V1* v1) { ... v1 ... }
		type(V2* v2) { ... v2 ... }
		type(V3* v3) { ... v3 ... } 
	}

By not naming the union components or providing explict access to the type
tag the only way to access the components is via a select statement.
Provided the variant is properly initialised this is statically
secure.

	The code

	v->print();

is also allowed if all the Vi have a print function, it expands to the
obvious select statement without the overhead of replicated source.
In general, any statement containing the name of a variant
(except initialisation) 
is nominally expanded into multiple statements in the obvious way.
I say 'nominally' because often we can restructure things to optimise
them, by expanding only up to the smallest unifying subexpression.

Variants are initialised in the obvious way, the static type of the
initialiser is used to set the tag field.

	V v=V1(); // v is now a V1

The usual function overloading/matching rules can be applied
in most places you expect.

***Problem 1**** assignment causes some problems. If interpreted
as user defined assignment, the type cannot be changed, if
the type changes like an initialisation then user defined assignments
will not perform as expected. Mm. BTW: this is the only big problem
I can think of.

It is clearly implied that one can have variant functions:

	f(V) { ... }

which of course accept a variant OR any of its components.

Now to the interesting bits. Naturally one must ask,
what happens if one specialises or instantiates a template
function with a variant or variant object? Do we generate
a single variant function or a family of ordinary functions?

The answer is .. it is implementation defined. It doesnt matter.
Variants commute with templates. 

There is more ... but this will do for a start.

Comments? Any help with the assignment problem?


-- 
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
	Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------
Newsgroups: comp.std.c++