Topic: Variants Vs. Signatures (was: Variants)


Author: cbergren@comm.mot.com (Craig Bergren)
Date: Thu, 27 May 1993 02:39:17 GMT
Raw View
John Max Skaller (maxtal@physics.su.OZ.AU) wrote:
> >In article 9546@mulga.cs.mu.OZ.AU, fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
> >
> >
> >Would variants still be required if a signature mechanism
> >for subtyping was available?

>  Good question. I think the answer is that
> variants are a facility for type inference. They respresent a union
> or *fixed* set of types.

>  Signatures are different: they are for 'ad hoc' polymorphism,
> that is, *any* class will do if it has the right signature.

>  Thus, signatures are not extensible as such, they
> consist of infinite types, including some types that 'accidentally'
> have the right signature.

>  Subclassing, on the other hand, respresents a single
> type, but with infinite implementations, or, if you prefer,
> it represents infinite *subtypes* of the type (subsets).

>  So we have three distinct mechanisms:

>  1) variant: instances are union of instances
>   of finite component types
>  2) signature: instances are infinite union of instances
>   of matching classes
>  3) subclassing: instances are subset of the single
>   base type instances


Maybe  this  is  a  problem  with  symantics;  your  explanation   about
signatures doesn't make sense to me if I read your terminology the way I
think it should read.  It appears to me that you are mixin' declarations
with instances.

All through this thread you have described that an instance of a variant
is an  instance  of ONE type  selected  from a finite set of types.  The
variant's  type is fixed  when it's  instanced.  If I can't  change  the
variant's type, can it be a union of instances of the union of types?  I
don't think so, unless your definition of union is exclusive or.  :-)

If I change 1) to replace declaration for instance I get:

     1) variant: declarations   are  union  of  declarations  of  finite
                 component types.

This statement seems to be consistent with the rest of the discussion on
variants, although gramatically incorrect.

So,  what is a  signature?  Is an  instance  of a  signature  ONE  class
(exclusive or) selected from a set of matching classes?

Let me re-state what I understand:

   An  instance  of a  variant  is an  instance  of  one  of  the  types
   enumerated in the variant declaration.

Is this true? If so, how 'bout this...

   An instance of a signature is an instance of one of the classes which
   has a member  function  matching  the  "signature"  of the  signature
   declaration.

--
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
!  The views I hold are not mine, nor those of my employer.                   !
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

I just read what I wrote.  The C++  jargon  namespace  is so  completely
polluted that I have to use "signature" is the non-reserved word meaning
of signature.  You know what it means, I hope.  :-)







Author: gregw@minotaur.tansu.com.au (Greg Wilkins)
Date: 21 May 1993 04:02:33 GMT
Raw View
In article 9546@mulga.cs.mu.OZ.AU, fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>gregw@minotaur.tansu.com.au (Greg Wilkins) writes:
>>void func2()
>>{
>>     V2 v2;
>>     cout << v2;   // I can see how this one would work.
>>     cin >> v2;    // but it would be really nice if you could solve this one
>>                   // in a general fashion.
>>}
>
>Both statements work fine. Any statement containing a variant expands to
>an implicit select statement over that variant. So func2() is equivalent to:
>
> void func2() {
>      V2 v2;
>      select (v2) {
>        type (int &v2_i) { cout << v2_i; }
>        type (char &v2_c) { cout << v2_c; }
>        type (MyClassWithoutCastToIntOp &v2_m) { cout << v2_m; }
>      };
>      select (v2) {
>        type (int &v2_i) { cin >> v2_i; }
>        type (char &v2_c) { cin >> v2_c; }
>        type (MyClassWithoutCastToIntOp &v2_m) { cin >> v2_m; }
>      };
> }

But you will not "know" the type of v2 until after the cin >> , unless you know
the type you are going to read in advance and use an appropriately initialized
variant.




>
>OK, now for the tricky example: assignment.
>
>>variant V1 [long, int];
>>variant V2 [int, char, MyClassWithoutCastToIntOp];
>>variant V3 [int, char];
>>
>>void func1()
>>{
>>  V1 v1;
>>  V2 v2;
>>  ...
>>  v1 = v2;       // is this legal??, does it produce a run time error if v2
>>                 // is not an int?, if v2 is a char, does it promote it to
>>    // an int?
>
>This is not legal.


>>  int i=v2;      // Does this produce a run time error for
>>    // MyClassWithoutCastToIntOp
>
>No, it produces a compile-time error.

>Essentially, variants don't support assignment. A variants type tag
>is set when it is initialized and it can't be modified afterwards.
>Variants are a bit like references in this respect.
>

Oh, I'm losing favour.

Variants are giving a very limited form of polymorphism:
if I can say:
   Class1 c1=1;
   Class2 c2=2;
   int i1 = c1;
   int i2 = c2;

Then I should be able to say:
    variant V1 [Class1,Class2];
    V1 va = c1;
    int i3 = v1;

Or at least
    V1 vb=c1;
    vb=c2;  // illegal??

Or at the very least
    V1 vc=c1;
    vc=vb;

Or am I missing something?

Would variants still be required if a signature mechanism for subtyping was available?

As I understand signatures,  if you wish to add a function (say doIt()) to an
arbitrary collection of classes (which may or may not have a common base class)
then you define:

signature SigWithDoIt
{
    virtual void doIt();
};

Then you can write:

void myClass::myMethod(SigWithDoIt s&)
{
   ...
   s.doIt();
   ...
}

Which will work for any object with a doIt() member.  Now the only thing that
variants have over this, is that all the select code is in one place (sometimes
good, sometimes bad), but all the definitions of XXXX::doIt() may be put into
the one file, and only the headers need to be modified  (or if you don't have
access to the source, derive a class and add doIt()).


And this is almost the same as defining a mixin class such as:

class DoItMixIn
{
   public:
     virtual void doIt()=0;
}

and then inheriting all doit capable classes from this mixin.

I know variants require a little less work to add a function, but unless they
provide full polymorphism, I think they will just lead to more work down the line.


-gregw





Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Fri, 21 May 1993 15:52:52 GMT
Raw View
In article <1thk8p$p0v@picasso.cssc-syd.tansu.com.au> gregw@minotaur.tansu.com.au writes:
>In article 9546@mulga.cs.mu.OZ.AU, fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>
>>Essentially, variants don't support assignment. A variants type tag
>>is set when it is initialized and it can't be modified afterwards.
>>Variants are a bit like references in this respect.
>
>Oh, I'm losing favour.

 Reserve judgement a bit if you can, for two reasons:

 1) you can implement discriminated unions with variants,
 and they *do* support type changing assignment

 2) why do variants not support assignment?
 there must be a reason, we must get something
 in return, surely?

The answer to (2) is yes: we get huge advantages in return
for this minor restriction that (1) can overcome anyhow.

What we get is a major new language feature that is assured
of fitting in with the rest of the language perfectly.

Variants are 'ideologically pure' if you like:-)

The things we can say that are important are things like
"variants commute with pointers" and "variants commute with
functions".

>
>Variants are giving a very limited form of polymorphism:
>if I can say:
>   Class1 c1=1;
>   Class2 c2=2;
>   int i1 = c1;
>   int i2 = c2;
>
>Then I should be able to say:
>    variant V1 [Class1,Class2];
>    V1 va = c1;
>    int i3 = v1;

 Yes, you can say that (provided you can interconvert
int and Class1 and int and Class2)
>
>Or at least
>    V1 vb=c1;
>    vb=c2;  // illegal??

 No, thats fine. Its exactly equivalent, in this case,
to saying

 Class 1 vb = c1;
 vb = c2;

>
>Or am I missing something?

 No, thats correct: you cant change the type of a variant,
but you can assign to it.

 The reason you cant change the type of a variant is that
it has the type of the object that it was initialised with.

>
>Would variants still be required if a signature mechanism
>for subtyping was available?

 Good question. I think the answer is that
variants are a facility for type inference. They respresent a union
or *fixed* set of types.

 Signatures are different: they are for 'ad hoc' polymorphism,
that is, *any* class will do if it has the right signature.

 Thus, signatures are not extensible as such, they
consist of infinite types, including some types that 'accidentally'
have the right signature.

 Subclassing, on the other hand, respresents a single
type, but with infinite implementations, or, if you prefer,
it represents infinite *subtypes* of the type (subsets).

 So we have three distinct mechanisms:

 1) variant: instances are union of instances
  of finite component types
 2) signature: instances are infinite union of instances
  of matching classes
 3) subclassing: instances are subset of the single
  base type instances

>
>As I understand signatures,  if you wish to add a function (say doIt()) to an
>arbitrary collection of classes (which may or may not have a common base class)
>then you define:
>
>signature SigWithDoIt
>{
>    virtual void doIt();
>};
>
>Then you can write:
>
>void myClass::myMethod(SigWithDoIt s&)
>{
>   ...
>   s.doIt();
>   ...
>}
>
>Which will work for any object with a doIt() member.

 Yes.

>Now the only thing that
>variants have over this, is that all the select code is in one place (sometimes
>good, sometimes bad), but all the definitions of XXXX::doIt() may be put into
>the one file, and only the headers need to be modified  (or if you don't have
>access to the source, derive a class and add doIt()).

 No: what variants have 'over' signatures is that they
offer a constraint in a different form to a signature.
Variants work for *any* set of types, whether there are methods
already in common or not, by allowing you to define those methods
ad hoc as you need them (using the select statement).

>And this is almost the same as defining a mixin class such as:
>
>class DoItMixIn
>{
>   public:
>     virtual void doIt()=0;
>}
>
>and then inheriting all doit capable classes from this mixin.
>
>I know variants require a little less work to add a function, but unless they
>provide full polymorphism, I think they will just lead to more work down the line.
>
 There would be *no point* adding a new language facility
that just provides the sort of polymorphism that we *already* have.

 I dont call the facility that variants offer polymorphism
at all: its constrained genericity and the constraint is not
an abstraction but a fixed set of types.

 The reason is that variants are designed for heterogneous
aggregates.


--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Fri, 21 May 1993 16:28:46 GMT
Raw View
Let me try on a new explanation of variants.

Consider this:

 int x = 1;
 long y = 2L;
 declare z = x + y;

Now what I intend here is *type inference*. The compiler deduces
the type of 'z' from the type of x+y.

Now consider this:

 variant declare [int, long, char*];

Now this is how I might define 'declare' above: there is
type inference, but it is constrained (which helps to resolve
ambiguities).

If I subsequently write:

 declare a = z;

then the compiler knows that 'z' is long, so it can deduce that 'a'
is also a long.

So: variants are type inference. As for type inference, the type
of a variant is not 'declare' or 'unknown' but is quite determined,
and an ordinary type. 'a' above is a long, it is not a variant,
because a variant is not a type, its a *facility* for type
inference which happens to allow constraints.

OK, step 2: you probably understand what I just said now,
but you might believe that type inference as I just described
was not really that useful: it just saves writing out the
types explicitly, right?

Yes, right. But that is exactly right: it saves *huge*
amounts of code writing, and the errors you might make.
COnsider now:

 f(variant [int, long] x)
 {
  return x*2;
 }

Now, in the light of the idea that variants are just type inference,
what does this mean?

Well, it means that there are really *two* functions:

 f(int x) { return 2*x; }
 f(long x) { return 2*x; }

and then

 f(1); // calls f(int)

by type inference .. or is it overloading .. oh, I see .. thats
the same thing : overload resolution *is* type inference already!

Now what do we gain here? Well, for a start, you only have to
write out the code for 'f' once with variants: the compiler
makes copies for each of the 'variant' types (int and long)
automatically.

But does it? Does it *have* to do this? The answer is no:
here is an alternative approach: the compiler makes *one*
function, and generates code for the two cases *inside* the
function. Then, when a call to 'f' is seen, it passes
a flag to 'f' in a hidden parameter, and 'f' switches
on that flag (selection :-)

Whats the  advantage of that? Well, consider:

 g( variant [int, long] x) {
  // stuff not refering to x
  // stuff not refering to x
  // stuff not refering to x
  // stuff not refering to x
  int y = x;
  // stuff not refering to x
  // stuff not refering to x
  // stuff not refering to x
  int z = x;
  // stuff not refering to x
 }

Now we dont want to needlessly duplicate all that stuff
that doesnt refer to 'x'. So we have one function, pass
a flag (the type tag :-) and thread the code wherever
there is a reference to the 'variant' x.

I'm sure you've all done this by hand (perhaps ashamedly :-)

The point is: it doesnt matter *which* technique is used:
thats implementation dependent. What is important
is that the original source is maintable as a single
piece of code, and the compiler takes care of handling
all the cases automatically.

What this comes down to is this: you can always write out
all the cases by hand, but type inference is much more
reliable, and the compiler is just writing out all the
cases by hand for you *automatically*.

Whats more, the compiler can optimise the code in a way
that is very hard for you to do by hand.

Well, how does this new explanation sound?
Can you see why variants are not types?
Can you see why you cant assign to a variant and change the type?
Can you see why variants are 100% secure and compatible with
the existing language?
Can you calculate how much duplicated code writing is saved
using variants?
Do you see how variants can be used to implement
'poor mans multiple dispatch'?
To implement 'constrained generic functions'?
To provide heterogenous containers?
Why variants "commute with pointers"?
Why they "commute with functions, templates and inheritance"?

More important: can you see why this facility is highly desirable?

OK, here are some harder questions:

Do variants commute with structs?
(If they do, discriminated unions cannot be implemented with variants,
so I hope the answer to this question is "no")

What are the rules for returning variants from virtual functions?
(Harder than it sounds: dont forget that we now have
covariant return types)


--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: g2devi@cdf.toronto.edu (Deviasse Robert N.)
Date: Sun, 23 May 1993 00:06:02 GMT
Raw View
maxtal@physics.su.OZ.AU (John Max Skaller) writes

> OK, here are some harder questions:
>
> What are the rules for returning variants from virtual functions?
> (Harder than it sounds: dont forget that we now have
> covariant return types)

Actually I don't see a problem. The current rules of virtual function return
types and the type safety requirements of variants seem to allow only one
possible rule for this case. Consider:

    class Eg{
       // ...
       virtual MyVariant1 f();
    };

    class EgEg:public Eg{
       // ...
       virtual MyVariant2 f();
    };


What is the most general rule that would conform both to the type safety of
variants and the restrictions of C++?
Assume the following definitions:
    variant Variant00 [/*possibly null*/];
    variant Variant0  [Variant00,/*possibly null*/];

Then the only possible cases that seem to be allowed are:
   (1) variant MyVariant1 [Variant0];
       variant MyVariant2 [Variant00];
   (2) variant MyVariant1 [Variant0,Eg*];
       variant MyVariant2 [Variant00,EgEg*];
   (3) variant MyVariant1 [Variant0,Eg&];
       variant MyVariant2 [Variant00,EgEg&];

Case (1) ensures that derived classes do not generate and extra types, thus
ensuring typesafety. It also corresponds to the case where the derived class
virtual functions have the same return type. Cases (2) and (3) allow covariant
return types, which doesn't break the typesafety either.

Am I missing something?

>
>
> --
>         JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
>  Maxtal Pty Ltd,      CSERVE:10236.1703
>         6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
>         NSW 2131, AUSTRALIA
>


--
/----------------------------------+------------------------------------------\
| Robert N. Deviasse               |"If we have to re-invent the wheel,       |
| EMAIL: g2devi@cdf.utoronto.ca    |  can we at least make it round this time"|
+----------------------------------+------------------------------------------/




Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Sun, 23 May 1993 02:04:26 GMT
Raw View
gregw@minotaur.tansu.com.au (Greg Wilkins) writes:

>fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:

>>Essentially, variants don't support assignment. A variants type tag
>>is set when it is initialized and it can't be modified afterwards.
>>Variants are a bit like references in this respect.
>>
>
>Oh, I'm losing favour.
[...]
>Or am I missing something?

You want a dunion rather than a variant.
The good news is that you can use variants to implement dunions,
and then just write:
 #include <dunion.h>
 variant V [...];
 void foo (Dunion<V>& x, Dunion<V>& y) {
  x = y;  // Dunions *do* support assignment
 }

>>>void func2()
>>>{
>>>     V2 v2;
>>>     cout << v2;   // I can see how this one would work.
>>>     cin >> v2;    // but it would be really nice if you could solve this one
>>>                   // in a general fashion.
>>>}
>>
>>Both statements work fine.
>
>But you will not "know" the type of v2 until after the cin >> , unless you know
>the type you are going to read in advance and use an appropriately initialized
>variant.

Ah, yes I see the problem now :-)
You want the statement cin >> v2 to change the type of v2.
Well, as I said, you can't change the type of a variant once it has
been initialized, but you can change the type of a dunion.
Now dunions don't directly support human-readable input,
but they *do* directly support binary I/O:
 void func2() {
  Dunion<V2> v2 = ...;
  v2.write_on(cout);
  v2.read_from(cin);
 }
Well, the Dunion class I posted in another article didn't support these
methods, but clearly they are very useful and also very easy to
implement, so they are worth adding. [Implementation below :-)]

The problem with human-readable input is: how do you decide which
type the value actually is? If you have a method for answering
this question in a particular case, then you can write your own
 operator << (ostream&, Dunion<V2>&)
which does what you want, but I don't see any general solution.

[*] Here's the implementation for those methods:
 template <class Variant>
 class Dunion {
  Struct_Containing_Variant val;
  ...
  void write_on(ostream &cout) const
   { cout.write( (char*)&val, sizeof(val) ); }
  void read_from(istream &cin)
   { cin.read( (char*)&val, sizeof(val) ); }
 };
In fact, given this implementation of Dunion, you could just use
 Dunion<V2> x;
 cout.write( (char*)&x, sizeof(x) );
but that wouldn't work if Dunion was implemented using a pointer to
a dynamically allocated Struct_Containing_Variant. Mind you, there
is no reason to implement Dunion using dynamic allocation except to
make it easier to understand: a real implementation use placement
operator new rather than dynamic allocation.

(question: why do these iostream methods take char* parameters instead
of void* ?)
--
Fergus Henderson                     This .signature virus might be
fjh@munta.cs.mu.OZ.AU                getting old, but you still can't
                                     consistently believe it unless you
Linux: Choice of a GNU Generation    copy it to your own .signature file!




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 23 May 1993 17:18:36 GMT
Raw View
In article <1993May23.000602.4919@cdf.toronto.edu> g2devi@cdf.toronto.edu (Deviasse Robert N.) writes:
>maxtal@physics.su.OZ.AU (John Max Skaller) writes
>
>> OK, here are some harder questions:
>>
>> What are the rules for returning variants from virtual functions?
>> (Harder than it sounds: dont forget that we now have
>> covariant return types)
>
>Actually I don't see a problem. The current rules of virtual function return
>types and the type safety requirements of variants seem to allow only one
>possible rule for this case. Consider:
>
>    class Eg{
>       // ...
>       virtual MyVariant1 f();
>    };
>
>    class EgEg:public Eg{
>       // ...
>       virtual MyVariant2 f();
>    };

 If MyVariant1 was a *value* return, then covariance would
not be allowed I think. Is that right? Covariance only applies
to pointer or reference returns? Why?

 Well, the thing hanging me up is that a variant
return is always a value in some sense. The 'tag' has to
be returned.

 So, rather than just converting a pointer,
we might need a complete re-initialisation of another
value to upcast the return.

>What is the most general rule that would conform both to the type safety of
>variants and the restrictions of C++?
>Assume the following definitions:
>    variant Variant00 [/*possibly null*/];
>    variant Variant0  [Variant00,/*possibly null*/];
>
>Then the only possible cases that seem to be allowed are:
>   (1) variant MyVariant1 [Variant0];
>       variant MyVariant2 [Variant00];
>   (2) variant MyVariant1 [Variant0,Eg*];
>       variant MyVariant2 [Variant00,EgEg*];
>   (3) variant MyVariant1 [Variant0,Eg&];
>       variant MyVariant2 [Variant00,EgEg&];
>
>Case (1) ensures that derived classes do not generate and extra types, thus
>ensuring typesafety. It also corresponds to the case where the derived class
>virtual functions have the same return type. Cases (2) and (3) allow covariant
>return types, which doesn't break the typesafety either.
>
>Am I missing something?
>
 Dont know :-)

 The real problem is that you cant 'upcast' a variant,
you can only *convert* from one to another. Thats because
variants are not types. That implies some sort of 'subset'
rule, or more precisely simply that

 virtual VBase Base::func();
 virtual VDerived  Derived::func();

then the rule is just that VDerived can convert to VBase,
where VDerived and VBase are variants.

Problem is, every ordinary type is also a singleton variant:

 T == variant [T]

so that in fact

 typedef VBase long;
 typedef VDerived int;

would leave the example legal .. except it isnt :-)



--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: g2devi@cdf.toronto.edu (Deviasse Robert N.)
Date: Sun, 23 May 1993 19:23:13 GMT
Raw View
In article <1993May23.171836.8111@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>In article <1993May23.000602.4919@cdf.toronto.edu> g2devi@cdf.toronto.edu (Deviasse Robert N.) writes:
>>maxtal@physics.su.OZ.AU (John Max Skaller) writes
>>
>>> OK, here are some harder questions:
>>>
>>> What are the rules for returning variants from virtual functions?
>>> (Harder than it sounds: dont forget that we now have
>>> covariant return types)
>>
>>Actually I don't see a problem. The current rules of virtual function return
>>types and the type safety requirements of variants seem to allow only one
>>possible rule for this case. Consider:
>>
>>    class Eg{
>>       // ...
>>       virtual MyVariant1 f();
>>    };
>>
>>    class EgEg:public Eg{
>>       // ...
>>       virtual MyVariant2 f();
>>    };
>
> If MyVariant1 was a *value* return, then covariance would
>not be allowed I think. Is that right? Covariance only applies
>to pointer or reference returns? Why?

But suppose we define:
    variant MyVariant1[Base*];
    variant MyVariant2[Derived*];
This should be allowed.

>
> Well, the thing hanging me up is that a variant
>return is always a value in some sense. The 'tag' has to
>be returned.
>
> So, rather than just converting a pointer,
>we might need a complete re-initialisation of another
>value to upcast the return.
>

No problem, the code splitting technique that makes covariant pointers and
references possible can be applied here.

>>What is the most general rule that would conform both to the type safety of
>>variants and the restrictions of C++?
>>Assume the following definitions:
>>    variant Variant00 [/*possibly null*/];
>>    variant Variant0  [Variant00,/*possibly null*/];
>>
>>Then the only possible cases that seem to be allowed are:
>>   (1) variant MyVariant1 [Variant0];
>>       variant MyVariant2 [Variant00];
>>   (2) variant MyVariant1 [Variant0,Eg*];
>>       variant MyVariant2 [Variant00,EgEg*];
>>   (3) variant MyVariant1 [Variant0,Eg&];
>>       variant MyVariant2 [Variant00,EgEg&];
>>
>>Case (1) ensures that derived classes do not generate and extra types, thus
>>ensuring typesafety. It also corresponds to the case where the derived class
>>virtual functions have the same return type. Cases (2) and (3) allow covariant
>>return types, which doesn't break the typesafety either.
>>
>>Am I missing something?
>>
> Dont know :-)
>
> The real problem is that you cant 'upcast' a variant,
>you can only *convert* from one to another. Thats because
>variants are not types. That implies some sort of 'subset'
>rule, or more precisely simply that
>
> virtual VBase Base::func();
> virtual VDerived  Derived::func();
>
>then the rule is just that VDerived can convert to VBase,
>where VDerived and VBase are variants.
>
>Problem is, every ordinary type is also a singleton variant:
>
> T == variant [T]
>
>so that in fact
>
> typedef VBase long;
> typedef VDerived int;
>
>would leave the example legal .. except it isnt :-)

It could have been, but the committee decided that it would be too confusing.
Personally I don't like this decision since it prevents one from using
covariant smart pointer return types. I mean
 typedef Ptr<Base> VBase;
 typedef Ptr<Derived> VDerived;
Anyway, generic smart pointers are not yet smart enough to be useful in this
case (but I have an extension in mind that will allow them to be:-) )

Getting back to the point, let me explain way I think that the above rules
are good enough and are not against the spirit of current C++ and the variant
proposal.

If VBase == VDerived then the current rules of C++ apply.

If we have
   variant VBase[VDerived,AnotherVariant];
then the derived class virtual function returns a VBase, so we have no type
safety rules.

If we have
    variant VBase[Base*];
    variant VDerived[Derived*];
or
    variant VBase[Base&];
    variant VDerived[Derived&];
then the current rules on covariant pointers/references should still apply.

Now the controversial cases,
given
    variant AnotherVariant[AnotherVariantSubset,RestOfVariant_PossiblyNull];
then
    variant VBase[AnotherVariant,Base*];
    variant VDerived[AnotherVariantSubset,Derived*];
or
    variant VBase[AnotherVariant,Base&];
    variant VDerived[AnotherVariantSubset,Derived&];
This case is a combination of the above two cases. Because of this, it would
be more confusing *not* to allow this case than the allow it. Also, it is
still typesafe so why not allow it.


So how would it be implemented? One method is via code splitting.
So that given appropriately defined VBase and VDerived
    struct Base{
       virtual VBase memberfn();
    };

    struct Derived{
       virtual VDerived memberfn();
    };

would be implemented as
    struct Base{
       VBase real_memberfn();
       virtual VBase virtual_memberfn()   { return Base::real_memberfn(); }
    };

    struct Derived{
       VDerived real_memberfn();
       virtual VBase virtual_memberfn()   { return Derived::real_memberfn(); }
    };

The compiler calls real_memberfn() if the non-virtual usage of the member
function is required and calls virtual_memberfn() if the virtual usage of the
member function is required.

So it's not difficult to implement and it doesn't appear to go against the
spirit of either the variant proposal or current C++.


>
>
>
>--
>        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
> Maxtal Pty Ltd,      CSERVE:10236.1703
>        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
>        NSW 2131, AUSTRALIA


Take care
    Robert
--
/----------------------------------+------------------------------------------\
| Robert N. Deviasse               |"If we have to re-invent the wheel,       |
| EMAIL: g2devi@cdf.utoronto.ca    |  can we at least make it round this time"|
+----------------------------------+------------------------------------------/