Topic: New strongly type enum... why not use "using"?


Author: "peter miller"<fuchsia.groan@virgin.net>
Date: Mon, 12 Sep 2011 11:18:26 -0700 (PDT)
Raw View
Hi Miles,

>  That sort of usage seems very common in existing code, by doing
>  something like:
>
>     enum X { a, b, X_MAX };
>     enum Y { d = X_MAX + 1, e, Y_MAX };


It is common. But surely the most C++ way to solve it, is with
a "using" declaration, i.e. change the enumerator-definition
(7.2/1) to be:

_enumerator-definition_:
  _enumerator_
  _enumerator_ = _constant-expression_
  using _enum-key_ _identifier_

so that you can write:

enum Y { using enum X, d, e, Y_max };

and the "using declaration" imports all members of X into Y.

The sensible value of d would be the last value in X, plus one;
but if it's easier for compiler-writers, it could be the highest
value in X, plus one.

This approach also allows me to code:

enum Z { a, b, c };

enum Z_count { using enum Z, Z_MAX };

enum { Z_MAX = Z_count::Z_MAX };

And now I don't pollute Z with Z_MAX - which always seemed
conceptually wrong to me.

Peter

--
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/


[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]




Author: Miles Bader<miles@gnu.org>
Date: Tue, 13 Sep 2011 09:29:08 -0700 (PDT)
Raw View
"peter miller"<fuchsia.groan@virgin.net>  writes:
>>   That sort of usage seems very common in existing code, by doing
>>   something like:
>>
>>      enum X { a, b, X_MAX };
>>      enum Y { d = X_MAX + 1, e, Y_MAX };
>
>  It is common. But surely the most C++ way to solve it, is with
>  a "using" declaration
...
>  enum Y { using enum X, d, e, Y_max };
>
>  and the "using declaration" imports all members of X into Y.

Hmm, "the most C++ way" is obviously a rather subjective matter.

Using "using" to simply import members from another enum does seem
intuitive enough, but having it change the following enumeration
values, or modify type-checking rules, seems pretty awkward.

It's especially awkward for the type-checking changes I mentioned --
and I think these are very desirable given typical usage -- because
"using ..."  occurs inside the body, so having an effect on the type
as a whole is quite weird [what happens, after all, if you put the
"using ..." at positions other that the beginning, which is something
that the syntax implies is OK, or use "using ..." multiple times?].

The thing is that what people are doing here really _is_ "inheritance",
so using C++'s usual inheritance syntax seems very natural to me.

-Miles

--
People who are more than casually interested in computers should have at
least some idea of what the underlying hardware is like.  Otherwise the
programs they write will be pretty weird.  -- Donald Knuth


[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]




Author: "peter miller"<fuchsia.groan@virgin.net>
Date: Thu, 15 Sep 2011 14:13:23 -0700 (PDT)
Raw View
Miles,

>  Using "using" to simply import members from another enum does
>  seem intuitive enough, but having it change the following
>  enumeration values,

It's no worse than this:

enum A
{
    x,
    y = 7,
    z
};

....and I thought it less surprising than this:

enum B
{
     a, // == 0
     using enum A,
     c, // == 1
};

....since it's just syntactic sugar for this:

enum B
{
     a, // == 0
     x = A::x,
     y = A::y,
     z = A::z,
     c, // == A::z + 1
};


And it has the extra benefit of being able to automatically
"count" the members in an enum. But I don't actually care; the
counter could be left unchanged.

>  or modify type-checking rules, seems pretty awkward.

How does it modify the type checking rules? There are no changes
I can see.

>  It's especially awkward for the type-checking changes I mentionedand I
>  think these are very desirable given typical usage

Actually, as Marcel Muller said, the problem with inheritance
is slicing; for example:

struct base_class { int a; };
struct derived_class : base_class { int b };

void s1(base_class);
void s2(derived_class d) { s1( d ); }
void s3() { s3( derived_class {} ); }

enum class base_enum { a };
enum class derived_enum : base_enum { b };

void f1(base_enum);
void f2(derived_enum d) { f1( d ); }
void f3() { f1( derived_enum::b ); }


The case with enums should be analogous to the case with
classes - if it's just inheritance. When s3() is called,
everything is fine; s1() is handed a sensible copy of
base_class. But what happens when f1() is called?

In my code I can find two cases:

case 1) f1() wouldn't understand the new constants and would
go into an error recovery mode - e.g. throw an exception.

There is no way for it to get a useful value of the "base
enum", as happens with classes.

In this case, inheritance has gained me nothing. (Well, it's
gained me some trouble - sloppy code lacking proper handling
for illegal enum values has become broken. And I don't want
to spend my time reauditing code.)

case 2) The constants are actually bitmasks and f1() would
work fine. In this case, my code already gets inheritance
through meta programming, e.g.

template<typename Enum>
auto
f4(Enum)
->  typename std::enable_if
<
  mpl::has_key<mpl::set<base_enum,derived_enum>,Enum>::value
>  ::type;

(If need be, this can wrap a function in a library.)

I suspect the above will cope with all the valid "inheritance"
cases.

The real problem is manually keeping enums in sync. If I
was in a forward looking mood, then the code would use a
macro, like this:

#define BIT_CONSTANTS bit0 = 1, bit1 = 2, bit2 = 4

enum C { BIT_CONSTANTS };
enum C_with_bells_on { BIT_CONSTANTS, bit3 = 8 };

So a using-declaration inside enums would remove another use of
the preprocessor.

And then there was the extra benefit of being able to
automatically count enum members.

>  -- because
>  "using ..."  occurs inside the body, so having an effect on the type
>  as a whole is quite weird [what happens, after all, if you put the
>  "using ..." at positions other that the beginning, which is something
>  that the syntax implies is OK, or use "using ..." multiple times?].

The obvious thing happens. It's just syntactic sugar/macro
alleviation.

(And if enums inherit, why not multiple enum "inheritance"?)

>  The thing is that what people are doing here really _is_ "inheritance",
>  so using C++'s usual inheritance syntax seems very natural to me.

To repeat what I said above. I can't find a real-world case
where inheritance of enums would do anything I can't already
do - except import identifiers quickly and easily. So that
was the problem I targeted.

Peter


--
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/


[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]