Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Inherited enum

98 views
Skip to first unread message

JiiPee

unread,
Jan 26, 2019, 9:49:34 AM1/26/19
to
enum which can be inherited would be a cool feature. I need it all the
time, like now.

Like this:

enum class AnimationNameBase {NoName};

struct Animation

{

AnimationNameBase m_name{AnimationNameBase::NoName};

void doAnimation(AnimationNameBase name) {

    m_name = name;

}

};


enum class PlayerAnimationNames : AnimationNameBase {WalkUp, WalkDown,
WalkRight};


Then using it:

Animation playerAnimation;

playerAnimation.doAnimation(PlayerAnimationNames::WalkUp);

// code.....then in some function:

if(playerAnimation.m_name == PlayerAnimationNames::WalkUp)

{

    // draw walk up

}

Alf P. Steinbach

unread,
Jan 26, 2019, 1:29:48 PM1/26/19
to
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.

For example,

// Here Wide_enum is a hypothetical extension of Narrow_enum, like,
// Wide_enum *inherits* all the values of Narrow_enum & adds more.

void foo_narrow( Narrow_enum x );
void foo_wide( Wide_enum x );

void bar()
{
const Wide_enum ew = Wide_enum::an_extra_value;
foo_narrow( ew ); // Oh noes, foo_narrow can't handle that.
}

void bar_tender()
{
const Narrow_enum en = Narrow_enum::one_of_the_original_values;
foo_wide( en ); // That's OK, foo_wide can handle that! :)
}

So the enum extension is in a sense the opposite of ordinary inheritance
in C++: instead of a specialization, it provides a generalization.

There is the same issue with number types, e.g. rational numbers extend
and generalize integers, so can't be reasonably modeled as a derived
class. I was surprised, half a decade ago or so, to find out that
Barbara Liskov did discuss this crucial point in her papers about Liskov
substitution. It's just that those who teach her works generally fail to
even mention this, like they don't understand how important it is.


Cheers!,

- Alf

Daniel

unread,
Jan 26, 2019, 5:51:30 PM1/26/19
to
There is a proposal for that

https://github.com/ginsbach/CppProposal/blob/release_70/ExtendedScopedEnumerations.pdf

There was a discussion about that on std-pr...@isocpp.org, which was
less than enthusiastic. As Alf notes, it's not inheritance, and it looks
strange to use the same syntax as inheritance for what is the opposite
relation (the value of the base enum is more constrained, as opposed to a
base class that is less constrained.)

Daniel

Chris Vine

unread,
Jan 26, 2019, 6:09:46 PM1/26/19
to
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. 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. 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).

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);

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.)

Chris Vine

unread,
Jan 26, 2019, 6:16:24 PM1/26/19
to
That should be:
class B2 {virtual void do_it(D1 d) {}; virtual ~B2(){};};

JiiPee

unread,
Jan 26, 2019, 6:17:07 PM1/26/19
to
On 26/01/2019 22:51, Daniel wrote:
> There is a proposal for that
>
> https://github.com/ginsbach/CppProposal/blob/release_70/ExtendedScopedEnumerations.pdf
>
> There was a discussion about that onstd-p...@isocpp.org, which was
> less than enthusiastic. As Alf notes, it's not inheritance, and it looks
> strange to use the same syntax as inheritance for what is the opposite
> relation (the value of the base enum is more constrained, as opposed to a
> base class that is less constrained.)


oh really? INteresting to know. Would be nice to get something like that....

JiiPee

unread,
Jan 26, 2019, 6:20:40 PM1/26/19
to
On 26/01/2019 22:51, Daniel wrote:
so I am not the only one thinking about this, :)

Alf P. Steinbach

unread,
Jan 26, 2019, 6:51:43 PM1/26/19
to
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>`...

Daniel

unread,
Jan 26, 2019, 7:17:37 PM1/26/19
to
On Saturday, January 26, 2019 at 6:20:40 PM UTC-5, JiiPee wrote:
>
> so I am not the only one thinking about this, :)

No. You might want to consider subscribing to the Google Group std-
prop...@isocpp.org, look up some of the articles discussing the proposal,
and contributing some examples along the lines of what the commentator below
is asking for:

"Mechanically WHAT you are proposing is pretty clear. You need to do a
better job on the WHY. Concretely, in your motivation section, I would find
and highlight some examples in real world open source code where
availability of this feature, if it was used, would make things better."

The chances of this proposal going anywhere is small, but your support could
make a difference, minute as it may be.

Daniel

Chris Vine

unread,
Jan 26, 2019, 7:39:48 PM1/26/19
to
On Sun, 27 Jan 2019 00:51:33 +0100
On further thought I agree with you on this. In thinking more about
it I have tried to persuade myself otherwise but I keep stubbing my toe
on the fact that a narrower enum has stronger invariants than a wider
enum: extensible enums would in fact be "open" - so yes, they would be
polymorphic - but not in a way which can be modelled by inheritance
sub-typing.

I also agree that having contravariant function arguments would
preclude them being used as out parameters.

JiiPee

unread,
Jan 27, 2019, 3:50:20 AM1/27/19
to
But, reading that article I am not sure if it can be used "like"
polymorhism with classes. So can I do what I did in my original message:

######

enum class AnimationNameBase {NoName};

struct Animation

{

AnimationNameBase m_name{AnimationNameBase::NoName};

void doAnimation(AnimationNameBase name) {

    m_name = name;

}

};

// in another file:
enum class PlayerAnimationNames : AnimationNameBase {WalkUp, WalkDown,
WalkRight};

#########

So that when I create PlayerAnimationNames I do not need to re-compile
the Animation-file? So is PlayerAnimationNames totally independent of
the Animation file? Like it is in polymorhism.


JiiPee

unread,
Jan 27, 2019, 3:53:35 AM1/27/19
to
On 27/01/2019 00:17, Daniel wrote:
> No. You might want to consider subscribing to the Google Group std-
> prop...@isocpp.org,


ok thanks

JiiPee

unread,
Jan 27, 2019, 3:57:59 AM1/27/19
to
On 27/01/2019 00:17, Daniel wrote:
> On Saturday, January 26, 2019 at 6:20:40 PM UTC-5, JiiPee wrote:
>> so I am not the only one thinking about this, :)
> No. You might want to consider subscribing to the Google Group std-
> prop...@isocpp.org,


As I said, I find myself needing this feature almost in all projects I
start. But its possible I should do things differently, but its also
possible I really need it, donno ... but I feel like I always need it :).

Programmers keep using std::string constants to make ID's for objects,
but I would rather use enum class type. But many times cannot do it
because with enum class the parent class file needs to be re-compiled
when adding new enum values etc. So then have to use old enum's like now
(so storing ID's /names as integers and using auto converting  from old
enums to int's).

Mr Flibble

unread,
Jan 27, 2019, 9:09:29 AM1/27/19
to
IDs for objects or object types? Use uint32_t or uint64_t for object ID
and UUID for object type. Enums are hard coded and brittle.

/Flibble

--
“You won’t burn in hell. But be nice anyway.” – Ricky Gervais

“I see Atheists are fighting and killing each other again, over who
doesn’t believe in any God the most. Oh, no..wait.. that never happens.” –
Ricky Gervais

"Suppose it's all true, and you walk up to the pearly gates, and are
confronted by God," Bryne asked on his show The Meaning of Life. "What
will Stephen Fry say to him, her, or it?"
"I'd say, bone cancer in children? What's that about?" Fry replied.
"How dare you? How dare you create a world to which there is such misery
that is not our fault. It's not right, it's utterly, utterly evil."
"Why should I respect a capricious, mean-minded, stupid God who creates a
world that is so full of injustice and pain. That's what I would say."

JiiPee

unread,
Jan 28, 2019, 2:09:18 AM1/28/19
to
On 27/01/2019 14:09, Mr Flibble wrote:
> On 27/01/2019 08:57, JiiPee wrote:
>> On 27/01/2019 00:17, Daniel wrote:
>>> On Saturday, January 26, 2019 at 6:20:40 PM UTC-5, JiiPee wrote:
>>>> so I am not the only one thinking about this, :)
>>> No. You might want to consider subscribing to the Google Group std-
>>> prop...@isocpp.org,
>>
>>
>> As I said, I find myself needing this feature almost in all projects
>> I start. But its possible I should do things differently, but its
>> also possible I really need it, donno ... but I feel like I always
>> need it :).
>>
>> Programmers keep using std::string constants to make ID's for
>> objects, but I would rather use enum class type. But many times
>> cannot do it because with enum class the parent class file needs to
>> be re-compiled when adding new enum values etc. So then have to use
>> old enum's like now (so storing ID's /names as integers and using
>> auto converting  from old enums to int's).
>
> IDs for objects or object types? Use uint32_t or uint64_t for object
> ID and UUID for object type. Enums are hard coded and brittle.
>
> /Flibble
>

I am talking about naming objects, like: PLAYER, BALL....

Öö Tiib

unread,
Jan 28, 2019, 5:29:51 AM1/28/19
to
If you need to inherit types then use classes (or structs or
class template instantiations) for such types. Unions or enums
can't be inherited from. Your code, translated to usage of struct:

#include <iostream>

struct AnimationNameBase
{
enum Value {NoName};
AnimationNameBase(int v) : v_(v) {}
AnimationNameBase& operator=(int v) {v_ = v; return *this;}
bool operator==(int v) const {return v_ == v;}
int v_{NoName};
};

struct Animation
{
AnimationNameBase m_name{AnimationNameBase::NoName};

void doAnimation(AnimationNameBase name)
{
m_name = name;
}
};


struct PlayerAnimationNames : AnimationNameBase
{
enum Value {WalkUp = NoName + 1, WalkDown, WalkRight};
};


int main()
{
Animation playerAnimation;

playerAnimation.doAnimation(PlayerAnimationNames::WalkUp);

// code.....then in some function:

if(playerAnimation.m_name == PlayerAnimationNames::WalkUp)
{
// draw walk up
}
std::cout << "kind of works™\n";
}

Personally to me such whole logic feels messed up, regardless if it is
enum or struct.

JiiPee

unread,
Jan 28, 2019, 6:23:10 PM1/28/19
to
Thanks, looks promising, I ll check that.

How elso it could be solved? If you have a parent class and you want to
store some kind of name there, how would you store? So that the name can
be used via inherited class objects.



Öö Tiib

unread,
Jan 29, 2019, 11:29:43 AM1/29/19
to
What is the problem, the "it", that you try to solve? Can you describe it?
I see you want inheritance of enums (IOW "what?") and also had code
example (IOW "how?") but I still do not understand "why?". The code looks
pointless. I could rewrite it but I have not needed it anywhere during
decades of development.

The whole concept of calling discrete integral constants (enumerators)
as "names" is alien to me. To me it sounds like a "kind", "brand", "breed",
"sort" or "variety" but no way a "name".

The thing what you try feels like attempt to do RTTI or virtual functions
manually. If so then use RTTI or virtual functions of C++, these are well
implemented. IOW it is fine to make a class for each "kind" of animations.
So inheritance goes naturally. The std::type_info can be used as ID of
"kind" but that is a bit smelly need.

If you really want an enum of kinds then keep just one master
list of all of the kinds that you have. I can see some usages to such
but do not see what benefits the version with inheritance gives.

JiiPee

unread,
Jan 30, 2019, 11:28:02 AM1/30/19
to
On 29/01/2019 16:29, Öö Tiib wrote:
> The whole concept of calling discrete integral constants (enumerators)
> as "names" is alien to me. To me it sounds like a "kind", "brand", "breed",
> "sort" or "variety" but no way a "name".


I did not mean literally "name" as an enum. But, like in your example
names would be that  "kind" or "brand". those are names for something...
thats what I meant. or "north" would be a name for direction to the
north etc.


I can give you an example of my real code: I create animations from an
png image which contains all the frames all animations for that
character. So if each animation contains 3 images for all directions,
then that image contains 3x4 frames (sub-images) =12.

I tell the animation object only the coordinates of the frames in that
image. And I would like to *name* all of them as well, like "WalkLeft1"
would be the frame of animation when walking to the left.

Thats why the animation base class is something like (or its has this
somewhere/somehow):

class Animation

{

FrameNames m_names;

// .... all other code for animation data

}

Then later on in some class object inherited from Animation
(characterAnimation) I might check (something like this):

if( character.isMovingLeft() )

{

draw( characterAnimation.getFrame(WalkLeft1) );

}


Point being that if I need to refer to a certain frame I can use an enum
to refer to it. And because the name of the animation frame is stored in
the parent class, enum values/type cannot be defined there (because the
enum is project related thing, not fixed class library thing). There can
be hundreds of different frame names in the project.


So, I would like to store the enum name of that frame into the Animation
class so I can use that name for each frame later on...




JiiPee

unread,
Jan 30, 2019, 11:45:40 AM1/30/19
to
On 29/01/2019 16:29, Öö Tiib wrote:
> I can see some usages to such
> but do not see what benefits the version with inheritance gives.


It gives the benefit that you can store the enum data/variable in the
parent class and then set it (its value) in the child class - and that
you do not need define the enum values in the parent class but can be
created in the child class.

Chris Vine

unread,
Jan 30, 2019, 3:33:05 PM1/30/19
to
I can't see why you need an enum to do that. On your description, you
should be using virtual functions implementing an interface, giving
the implementation classes the names that you seem to want, or better
still using higher order functions (functions taking std::function
objects which implement the required behaviour).

Or possibly you want to use virtual functions or higher order functions
to implement the visitor pattern (but from what you describe that seems
much more heavyweight than you need).

Or possibly you just want a global unordered map with a global enum as
key to a set of pre-defined animation actions: it is safe to add values
to such a global enum textually on need as you extend your
implementations, and this doesn't break the API or ABI.

Leaving that aside, as Alf has pointed out, you cannot use enums the
way you propose. It breaches the substitution principle and looks like
an incorrect re-implementation of virtual functions.
0 new messages