On 27.01.2019 00:09, Chris Vine wrote:
> On Sat, 26 Jan 2019 19:29:34 +0100
> "Alf P. Steinbach" <
alf.p.stein...@gmail.com> wrote:
>> On 26.01.2019 15:49, JiiPee wrote:
>>> enum which can be inherited would be a cool feature. I need it all the
>>> time, like now.
>>>
>> [snipped example of hypothetical extension of an enum]
>>
>> Well, there is little snag, namely that extension of an enum class, with
>> more values, doesn't produce a derived class but a base class.
>
> I am not sure that is the way to look at it. In terms of variance, an
> extension of an enum _would_ provide a sub-type of the enum.
I generally disagree, but since C++ enums are very low level it's
possible to view a C++ enum type as having all the values possible with
the enum's underlying type, just that some values have been named.
With that view an extension just provides more value names, and so is
essentially equivalent to the enum that it extends.
As I think of it the sub/super type concept enters the picture only when
a more restricted view of enums is adopted, where each possible value
has a single unique name in the enumeration, and each named value has a
*meaning*.
Then two extensions of enum Base, E1 and E2, can both introduce names
for the underlying type's value 42, that's not used in Base. With E1 the
name of 42 reflects the meaning "we have lift-off and rocket is away,
apply the water cannons", while with E2 the name of 42 reflects the
meaning "that dang rocket isn't moving, pump more fuel to it".
Maybe function foo, being given E2(42) as actual argument for a Base
parameter, and knowing only about the Base values, applies water cannons
to the bottom of the rocket that isn't moving yet. Because at the time
foo was written, the only Base extension yet was E1. Serious SNAFU.
That exemplifies, that if sub-type means that an instance of it can be
passed where the formal parameter is of a supertype, then every instance
of a sub-type should be an instance of the supertype - more generally,
one should be able to treat every instance as an instance of the
supertype (this is the good old Liskov substitution principle, the LSP).
For enum extensions that's opposite.
Ignoring that property you risk getting very mutually incompatible enum
extensions used as actual arguments for a parameter of the basic
original enum type.
> The more
> conventional way of looking at your example below is that function
> arguments can safely be contravariant but not covariant, and that
> return values can be covariant and not contravariant.
Pure "in"-arguments can be safely contravariant.
They need to be const all the way.
C++'s lack of support contravariance is consistent with and in my view
connected with its lack of support for designating arguments as "in",
"in/out" or "out", except that return values are pure "out".
> In either case,
> safe conversions are possible: in the case of enum arguments, any
> function which can handle the wider case (the sub-type) can handle the
> narrow case (the super-type).
Not agreeing with the sub/super designations.
With normal object oriented inheritance (or extension, as it's called in
Eiffel) of sound design, the extension is a sub-type.
That terminology can't be transferred to enums without losing the deeper
meaning, the reason that they're called that, namely that any instance
of a sub-type can be used where a general instance of the super-type is
expected. In this sense the super-type encompasses the sub-type. Every
instance of the sub-type IS-A super-type instance; every camel is a
mammal, the set of mammals is very much larger than the set of camels.
It's not the case that every named value of an extended enum is a named
value of the base enum.
That relationship goes the other way: every named value of the base enum
IS-A named value of the extended enum, so the original base enum is the
sub-type, and the extension is the sub-type, in this relationship.
> For virtual functions C++ is invariant on its argument types and
> covariant on its return types and so sound (on that issue) but more
> restrictive than necessary. This would be OK type-wise but would not be
> accepted by C++:
>
> class B1 {virtual ~B1();};
> class D1 : public B1 {};
>
> class B2 {void do_it(D1 d) {}; virtual ~B2(){};};
> class D2: public B2 {void do_it(B1 b) override {};}; // C++ objects
>
> D1 d1;
> D2 d2;
> d2.do_it(d1);
Due to the possibility of pointer data members etc. there is no way for
a C++ compiler to see, in general, that `D1 d` designates a pure "in"
argument for which contra-variance would be OK.
But so much else in C++ puts the burden of being sure, on the
programmer, so why not also here?
I believe that's because there's been no big need for this feature.
> Your bar_tender() is also legitimate type-wise as an example of
> contravarience but I don't think you can actually write that code
> legally in C++. (However you might well be able to prove me wrong on
> that - you might be able to do something clever with templates, I am
> not sure.)
One would need to use some Java-like class based enum lookalikes, with
implicit value conversion defined instead of inheritance based reference
conversion.
It's a problem & solution that pops up with smart pointers, e.g.
conversion of `shared_ptr<Derived>` to `shared_ptr<Base>`.
You really wouldn't want to have `shared_ptr<Derived>` IS-A
`shared_ptr<Base>`...