enum_cast proposal

309 views
Skip to first unread message

m.ce...@gmail.com

unread,
Jan 3, 2017, 4:56:05 AM1/3/17
to ISO C++ Standard - Future Proposals
Hi,

I propose to add a enum_cast function that safely casts int value to a target enum type if int value represents a valid enumerator.

template <typename Enum, typename Int>
optional
<Enum> enum_cast(Int value);

requires
: is_enum<Enum>, is_integral<Int>

enum color
{
   red
= 1,
   green
= 200,
   blue
= 3
};

static_assert(enum_cast<color>(200u) == color::green);
static_assert(enum_cast<color>(3) == color::blue);
static_assert(enum_cast<color>(4) == nullopt);


This should work for both scoped and unscoped enums.
For opaque enums this would only check if value fits in underlying type.

Why do we want this:
 - validation of input data (e.g. coming from user or deserialization).
 - other?

I know this could be purely library extension if based on reflection, but since we don't know when we will get reflection,
and implementation based on reflection may not be optimal,
I think it should be implemented with compiler support (i.e. via __builtin_* intrinsic).

Regards,
Maciej

Thiago Macieira

unread,
Jan 3, 2017, 9:25:25 AM1/3/17
to std-pr...@isocpp.org
On terça-feira, 3 de janeiro de 2017 01:56:05 BRST m.ce...@gmail.com wrote:
> template <typename Enum, typename Int>
> optional<Enum> enum_cast(Int value);
>
> requires: is_enum<Enum>, is_integral<Int>
>
> enum color
> {
> red = 1,
> green = 200,
> blue = 3
> };

How do you see compilers implementing:

void f(int x)
{
enum_cast<color>(x);
}

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

m.ce...@gmail.com

unread,
Jan 3, 2017, 10:06:10 AM1/3/17
to ISO C++ Standard - Future Proposals
W dniu wtorek, 3 stycznia 2017 15:25:25 UTC+1 użytkownik Thiago Macieira napisał:
On terça-feira, 3 de janeiro de 2017 01:56:05 BRST m.ce...@gmail.com wrote:
> template <typename Enum, typename Int>
> optional<Enum> enum_cast(Int value);
>
> requires: is_enum<Enum>, is_integral<Int>
>
> enum color
> {
>    red = 1,
>    green = 200,
>    blue = 3
> };

How do you see compilers implementing:

void f(int x)
{
        enum_cast<color>(x);
}



Compiler should synthesize a function like below:

optional<color> enum_cast(int x)
{
   
switch(x)
   
{
       
case red:
       
case green:
       
case blue:
           
return static_cast<color>(x);
       
default:
           
return nullopt;
   
}
}

Of course it could implement it more efficently (as range check instead of switch if all enumerators are consecutive integers).

Regards,
Maciej

D. B.

unread,
Jan 3, 2017, 10:08:47 AM1/3/17
to std-pr...@isocpp.org
Do you expect the handle the common case where people use enums as bitmasks of different enumerators?

Nicol Bolas

unread,
Jan 3, 2017, 10:21:37 AM1/3/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com
On Tuesday, January 3, 2017 at 4:56:05 AM UTC-5, m.ce...@gmail.com wrote:
Hi,

I propose to add a enum_cast function that safely casts int value to a target enum type if int value represents a valid enumerator.

It should not return an `optional`. It should either correctly cast or throw. Why?

Consider how users will use it. You're going to see a lot of people doing `enum_cast<>().value()`. Which will incur needless copying if the conversion worked. And if it doesn't work, an exception will be thrown.

What exception will that be? `bad_optional_access`. Which is about as useless as exceptions get as far as tracking down the problem. A far more descriptive exception would be `bad_enum_cast`.

I would say that you should provide both APIs: `enum_cast` for conversion or throwing. And `optional_enum_cast` or somesuch for conversion into an `optional<enum>`.

Thiago Macieira

unread,
Jan 3, 2017, 10:56:35 AM1/3/17
to std-pr...@isocpp.org
On terça-feira, 3 de janeiro de 2017 07:06:10 BRST m.ce...@gmail.com wrote:
> Compiler should synthesize a function like below:
>
> optional<color> enum_cast(int x)
> {
> switch(x)
> {
> case red:
> case green:
> case blue:
> return static_cast<color>(x);
> default:
> return nullopt;
> }
> }

How do you know whether a use of enum_cast is compile-time or whether it is
runtime?

static_cast is always compile time and always succeeds or produces a
compilation error. Therefore, checking at runtime is unnecessary.

dynamic_cast is almost always runtime and its result needs to be checked at
runtime.

Also, where did that optional come from? If it's going to use optional, then
it needs to be a library feature.

> Of course it could implement it more efficently (as range check instead of
> switch if all enumerators are consecutive integers).

One would expect the compilers to be smart enough to do that with a switch
block that is contiguous. Still, QoI issue.

m.ce...@gmail.com

unread,
Jan 3, 2017, 11:02:02 AM1/3/17
to ISO C++ Standard - Future Proposals

W dniu wtorek, 3 stycznia 2017 16:08:47 UTC+1 użytkownik D. B. napisał:
Do you expect the handle the common case where people use enums as bitmasks of different enumerators?


No, I don't think so.

But such a function for sure can be written using enum_cast:
go through each bit and test each one of them with enum_cast.

Regards,
Maciej

m.ce...@gmail.com

unread,
Jan 3, 2017, 11:12:13 AM1/3/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com
At this point I consider chosing a error reporting way bike-shedding.

Let's focus on actual functionality it provides for now:
check if provided int corresponds to any of the enumerators of given enum.

m.ce...@gmail.com

unread,
Jan 3, 2017, 11:12:29 AM1/3/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com
W dniu wtorek, 3 stycznia 2017 16:21:37 UTC+1 użytkownik Nicol Bolas napisał:

m.ce...@gmail.com

unread,
Jan 3, 2017, 11:22:53 AM1/3/17
to ISO C++ Standard - Future Proposals
W dniu wtorek, 3 stycznia 2017 16:56:35 UTC+1 użytkownik Thiago Macieira napisał:
On terça-feira, 3 de janeiro de 2017 07:06:10 BRST m.ce...@gmail.com wrote:
> Compiler should synthesize a function like below:
>
> optional<color> enum_cast(int x)
> {
>     switch(x)
>     {
>         case red:
>         case green:
>         case blue:
>             return static_cast<color>(x);
>         default:
>             return nullopt;
>     }
> }

How do you know whether a use of enum_cast is compile-time or whether it is
runtime?


I don't know and I don't care. The function should be constexpr to support both cases. 
 
static_cast is always compile time and always succeeds or produces a
compilation error. Therefore, checking at runtime is unnecessary.

dynamic_cast is almost always runtime and its result needs to be checked at
runtime.

Also, where did that optional come from? If it's going to use optional, then
it needs to be a library feature.

Just a first idea on dealing with failure cases. Not really important at this point.

What I'd like to hear now is whether anyone else would find such a functionality useful?
Do you consider it a good idea to put it into C++ standard/library?

Vicente J. Botet Escriba

unread,
Jan 3, 2017, 12:53:05 PM1/3/17
to std-pr...@isocpp.org
Le 03/01/2017 à 16:56, Thiago Macieira a écrit :
> On terça-feira, 3 de janeiro de 2017 07:06:10 BRST m.ce...@gmail.com wrote:
>
> Also, where did that optional come from? If it's going to use optional, then
> it needs to be a library feature.
I'm not for the optional return type, but we have already a lot of cases
where syntactic sugar as range-based for loop and structure binding
depends on library.
So I'm not sure this could be an argument against optional.

Vicente

Vicente J. Botet Escriba

unread,
Jan 3, 2017, 1:25:20 PM1/3/17
to std-pr...@isocpp.org
Hi, I believe this could be useful. Library implementers could be free to use builtins or reflection once it is there.

Now, I believe that we need two kind of functions. One that is a cast and that says just that we are casting. It would be the same as a static_cast. Something like gsl::narrow_cast so that we express better the intent. In addition we need the function you are proposing similar to gsl::narrow.
The question is how the function reports errors. If we use exceptions, I agree with Nicol that a specific exception would be better.

I will then propose
* enum_cast ~static_cast
* to_enum : throw exception if error
* try_to_enum : returns optional<Enum> or an interface based on error_code (like from_chars). status_value and expected can be considered also once adopted.
Do we expect several error conditions?

The fist is just a library solution. For the last two, we would need a compiler builtin or reflection so that both can be easily and efficiently implemented.

My 2cts.
Vicente

P.S. I don't expect this to be available before reflection, so an implementation using the current reflection (see github) interface should be welcome. This could be important to balance the compiler effort.

Nicol Bolas

unread,
Jan 3, 2017, 1:43:41 PM1/3/17
to ISO C++ Standard - Future Proposals

What's the point of `enum_cast`, save the fact that it would SFINAE on the given type being an actual enumeration? `static_cast` is the standard way of saying "make this integer an enum without checking". I don't think we need another way to spell that.
 
* to_enum : throw exception if error
* try_to_enum : returns optional<Enum> or an interface based on error_code (like from_chars). status_value and expected can be considered also once adopted.
Do we expect several error conditions?

Well, there's only one failure state: the given value is not in the enumeration. As such, I see no need for using complicated objects that store errors; `optional` should be sufficient.

Thiago Macieira

unread,
Jan 3, 2017, 2:09:19 PM1/3/17
to std-pr...@isocpp.org
On terça-feira, 3 de janeiro de 2017 18:53:02 BRST Vicente J. Botet Escriba
wrote:
Those are quite different things. In fact, they show how to *not* depend on
the library in the first place. Ranged fors and structured bindings only
require that functions called begin, end, and get exist. So I can add any
function of my own with those names and it will work. And structured bindings
are very specifically *not* a std::tuple. They're something different.

So enum_cast could return an optional-like opaque type defined by the compiler
(like structured bindings and lambdas) which can be converted to std::optional
on construction.

Thiago Macieira

unread,
Jan 3, 2017, 2:12:38 PM1/3/17
to std-pr...@isocpp.org
On terça-feira, 3 de janeiro de 2017 08:22:52 BRST m.ce...@gmail.com wrote:
> > How do you know whether a use of enum_cast is compile-time or whether it
> > is
> > runtime?
>
> I don't know and I don't care. The function should be constexpr to support
> both cases.

Well, I think people may care. A constexpr invocation has zero cost at
runtime, since it will have failed to convert by producing an error message,
or it will succeed by becoming a no-op (like static_cast, minus pointer
adjustment). But a non-constexpr invocation will have a runtime cost.

> What I'd like to hear now is whether anyone else would find such a
> functionality useful?
> Do you consider it a good idea to put it into C++ standard/library?

Maybe you should rename it to mean a check if the value is a valid enumerator
and drop the "cast" part.

Thiago Macieira

unread,
Jan 3, 2017, 2:15:39 PM1/3/17
to std-pr...@isocpp.org
On terça-feira, 3 de janeiro de 2017 10:43:41 BRST Nicol Bolas wrote:
> What's the point of `enum_cast`, save the fact that it would SFINAE on the
> given type being an actual enumeration? `static_cast` is the standard way
> of saying "make this integer an enum without checking". I don't think we
> need another way to spell that.

One of two ways. You can always just use the enumeration in constructor style:

Enum(1) <=> static_cast<Enum>(1)

This is a matter of coding convention.

Nicol Bolas

unread,
Jan 3, 2017, 2:59:46 PM1/3/17
to ISO C++ Standard - Future Proposals


On Tuesday, January 3, 2017 at 2:09:19 PM UTC-5, Thiago Macieira wrote:
On terça-feira, 3 de janeiro de 2017 18:53:02 BRST Vicente J. Botet Escriba
wrote:
> Le 03/01/2017 à 16:56, Thiago Macieira a écrit :
> > On terça-feira, 3 de janeiro de 2017 07:06:10 BRST m.ce...@gmail.com
> > wrote:
> >
> > Also, where did that optional come from? If it's going to use optional,
> > then it needs to be a library feature.
>
> I'm not for the optional return type, but we have already a lot of cases
> where syntactic sugar as range-based for loop and structure binding
> depends on library.
> So I'm not sure this could be an argument against optional.

Those are quite different things. In fact, they show how to *not* depend on
the library in the first place. Ranged fors and structured bindings only
require that functions called begin, end, and get exist. So I can add any
function of my own with those names and it will work. And structured bindings
are very specifically *not* a std::tuple. They're something different.

So enum_cast could return an optional-like opaque type defined by the compiler
(like structured bindings and lambdas) which can be converted to std::optional
on construction.

First, a correction. Structured binding does not deal in an "opaque type defined by the compiler". It deals in exactly whatever type the initializing expression resolves to. So long as the type has the specified characteristics or provides the interface required, it will work. But the compiler is not inventing some type behind-the-scenes.

Further, I think your analogy is rather wrongheaded. Ranges and product types are categories of types provided by the user. The language is defining mechanisms for the user to provide a type which can be consumed by the language. `enum_cast` is not like that at all. It returns a type, based on basic values provided by the user. For the optional-version of it, that type would have all of the properties of `optional`.

Lastly, having `enum_cast` work via some compiler-defined type is rather extreme for something as simplistic as this, don't you think? There are only so many ways to implement an "optional", and you can define implicit conversions from `std::optional` type to yours with little penalty. The conversion can even be `constexpr`, just like `std::optional`, so you would suffer no loss of performance in constexpr contexts.

Vicente J. Botet Escriba

unread,
Jan 3, 2017, 5:04:22 PM1/3/17
to std-pr...@isocpp.org
Le 03/01/2017 à 19:43, Nicol Bolas a écrit :


On Tuesday, January 3, 2017 at 1:25:20 PM UTC-5, Vicente J. Botet Escriba wrote:
Le 03/01/2017 à 10:56, m.ce...@gmail.com a écrit :
Hi,

I propose to add a enum_cast function that safely casts int value to a target enum type if int value represents a valid enumerator.

template <typename Enum, typename Int>
optional
<Enum> enum_cast(Int value);

requires
: is_enum<Enum>, is_integral<Int>


Why do we want this:
 - validation of input data (e.g. coming from user or deserialization).
 - other?

I know this could be purely library extension if based on reflection, but since we don't know when we will get reflection,
and implementation based on reflection may not be optimal,
I think it should be implemented with compiler support (i.e. via __builtin_* intrinsic).

Hi, I believe this could be useful. Library implementers could be free to use builtins or reflection once it is there.

Now, I believe that we need two kind of functions. One that is a cast and that says just that we are casting. It would be the same as a static_cast. Something like gsl::narrow_cast so that we express better the intent. In addition we need the function you are proposing similar to gsl::narrow.
The question is how the function reports errors. If we use exceptions, I agree with Nicol that a specific exception would be better.

I will then propose
* enum_cast ~static_cast

What's the point of `enum_cast`, save the fact that it would SFINAE on the given type being an actual enumeration? `static_cast` is the standard way of saying "make this integer an enum without checking". I don't think we need another way to spell that.
What is the point of narrow_cast? I believe it is useful to know the kind of cast we are using. This helps to inspect the code we (others) have written in a more efficient way.
 
* to_enum : throw exception if error
* try_to_enum : returns optional<Enum> or an interface based on error_code (like from_chars). status_value and expected can be considered also once adopted.
Do we expect several error conditions?

Well, there's only one failure state: the given value is not in the enumeration. As such, I see no need for using complicated objects that store errors; `optional` should be sufficient.
If there is only one error case, yes optional is a good candidate.
However it would be weird to have a different exception depending on whether the user uses to_enum or try_to_enum (or whatever names are more appropriated)


Vicente

gmis...@gmail.com

unread,
Jan 3, 2017, 6:01:16 PM1/3/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com

How about:

enum class trafflic_light{ unknown, reg, yellow, green };


traffic_light = traffic_light::unknown; // My default.
bool success = make_enum(light, x); // Does not set light on failure. X of any integral type
if (!success)
 throw std::logic_error("Error: Expected a value compatible with a traffic_light enum.");
if (light == traffic_light::green)
 cpp20_you_are_our_only_hope();

// make_enum - Standard special funciton. Make this available in C too.

Thiago Macieira

unread,
Jan 3, 2017, 6:15:02 PM1/3/17
to std-pr...@isocpp.org
On terça-feira, 3 de janeiro de 2017 11:59:46 BRST Nicol Bolas wrote:
> Lastly, having `enum_cast` work via some compiler-defined type is rather
> extreme for something as simplistic as this, don't you think? There are
> only so many ways to implement an "optional", and you can define implicit
> conversions from `std::optional` type to yours with little penalty. The
> conversion can even be `constexpr`, just like `std::optional`, so you would
> suffer no loss of performance in constexpr contexts.

Anything that is a compiler feature (a builtin or a core language expression)
needs to be able to choose between std::optional, std::experimental::optional
and std::__1::optional by just changing #includes.

A library solution needs not.

It seems to me that a library solution wrapping a compiler builtin intrinsic
is the only solution possible. And using reflection actually matches that
requirement, albeit indirectly.

gmis...@gmail.com

unread,
Jan 3, 2017, 6:48:25 PM1/3/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com

Just considering my earlier make_enum comment further, The ultimate minimum for C++ is surely this?:

traffic_light light{}; // whatever default.
bool guaranteed_success = std::can_cast_enum<traffic_light>(x);
if (guaranteed_success)
 light = reinterpret_cast<traffic_light>(x);
// else error condition.

It separates the validity check from the conversion and then you can build whatever on that, i.e. assign, atomic assign, etc.
This seems more C++ like, but forgets C, which probably people feel is not a concern anyway.
But other wise some special C and C++ syntax like  can_cast_enum(traffic_light, x) would seem to cover both languages if that's desirable.


On Tuesday, January 3, 2017 at 10:56:05 PM UTC+13, m.ce...@gmail.com wrote:

Nicol Bolas

unread,
Jan 3, 2017, 10:43:33 PM1/3/17
to ISO C++ Standard - Future Proposals


On Tuesday, January 3, 2017 at 6:15:02 PM UTC-5, Thiago Macieira wrote:
On terça-feira, 3 de janeiro de 2017 11:59:46 BRST Nicol Bolas wrote:
> Lastly, having `enum_cast` work via some compiler-defined type is rather
> extreme for something as simplistic as this, don't you think? There are
> only so many ways to implement an "optional", and you can define implicit
> conversions from `std::optional` type to yours with little penalty. The
> conversion can even be `constexpr`, just like `std::optional`, so you would
> suffer no loss of performance in constexpr contexts.

Anything that is a compiler feature (a builtin or a core language expression)
needs to be able to choose between std::optional, std::experimental::optional
and std::__1::optional by just changing #includes.

... why? std::experimental::optional isn't part of the standard. And I have no idea what `std::__1::optional` is.

Nicol Bolas

unread,
Jan 3, 2017, 10:52:18 PM1/3/17
to ISO C++ Standard - Future Proposals


On Tuesday, January 3, 2017 at 5:04:22 PM UTC-5, Vicente J. Botet Escriba wrote:
Le 03/01/2017 à 19:43, Nicol Bolas a écrit :


On Tuesday, January 3, 2017 at 1:25:20 PM UTC-5, Vicente J. Botet Escriba wrote:
Le 03/01/2017 à 10:56, m.ce...@gmail.com a écrit :
Hi,

I propose to add a enum_cast function that safely casts int value to a target enum type if int value represents a valid enumerator.

template <typename Enum, typename Int>
optional
<Enum> enum_cast(Int value);

requires
: is_enum<Enum>, is_integral<Int>


Why do we want this:
 - validation of input data (e.g. coming from user or deserialization).
 - other?

I know this could be purely library extension if based on reflection, but since we don't know when we will get reflection,
and implementation based on reflection may not be optimal,
I think it should be implemented with compiler support (i.e. via __builtin_* intrinsic).

Hi, I believe this could be useful. Library implementers could be free to use builtins or reflection once it is there.

Now, I believe that we need two kind of functions. One that is a cast and that says just that we are casting. It would be the same as a static_cast. Something like gsl::narrow_cast so that we express better the intent. In addition we need the function you are proposing similar to gsl::narrow.
The question is how the function reports errors. If we use exceptions, I agree with Nicol that a specific exception would be better.

I will then propose
* enum_cast ~static_cast

What's the point of `enum_cast`, save the fact that it would SFINAE on the given type being an actual enumeration? `static_cast` is the standard way of saying "make this integer an enum without checking". I don't think we need another way to spell that.
What is the point of narrow_cast?

... I don't know. But I'm pretty sure it's not part of the C++ standard, so why are you asking?
 
I believe it is useful to know the kind of cast we are using. This helps to inspect the code we (others) have written in a more efficient way.

It's a static_cast. That's the kind of cast you're using.

I have a strong dislike of proliferating non-standard cast functions. Pointer-based casts that are equivalent to standard library casts, those are fine. But inventing a new cast with its own special behavior? Why?
 
* to_enum : throw exception if error
* try_to_enum : returns optional<Enum> or an interface based on error_code (like from_chars). status_value and expected can be considered also once adopted.
Do we expect several error conditions?

Well, there's only one failure state: the given value is not in the enumeration. As such, I see no need for using complicated objects that store errors; `optional` should be sufficient.
If there is only one error case, yes optional is a good candidate.
However it would be weird to have a different exception depending on whether the user uses to_enum or try_to_enum (or whatever names are more appropriated)

The function returning an `optional` doesn't throw an exception. The exception is thrown by the improper use of `optional`.

Remember: the user who asked for an `optional` wants the behavior of an `optional`. Which almost certainly means that they're not going to call `value` without actually checking its value. If they were happy with an exception being thrown, they wouldn't be using the `optional` version.

Let's not assume that the user has no idea how to use the API.

Nicol Bolas

unread,
Jan 3, 2017, 10:58:53 PM1/3/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com
On Tuesday, January 3, 2017 at 6:01:16 PM UTC-5, gmis...@gmail.com wrote:

How about:

enum class trafflic_light{ unknown, reg, yellow, green };


traffic_light = traffic_light::unknown; // My default.
bool success = make_enum(light, x); // Does not set light on failure. X of any integral type

No, that requires the existence of `light`. C++ has too many APIs that needlessly require live objects already; we don't need another one.


On Tuesday, January 3, 2017 at 6:48:25 PM UTC-5, gmis...@gmail.com wrote:

Just considering my earlier make_enum comment further, The ultimate minimum for C++ is surely this?:

traffic_light light{}; // whatever default.
bool guaranteed_success = std::can_cast_enum<traffic_light>(x);
if (guaranteed_success)
 light = reinterpret_cast<traffic_light>(x);
// else error condition.

It separates the validity check from the conversion

That's actually a pretty good idea. Of course, there should be "higher-level" functionality like a direct `enum_cast` that throws and so forth, as discussed. But this way, people have the lowest-level tools to build their own functionality on top of the basic question.

Vicente J. Botet Escriba

unread,
Jan 4, 2017, 2:13:32 AM1/4/17
to std-pr...@isocpp.org, m.ce...@gmail.com
Le 04/01/2017 à 00:48, gmis...@gmail.com a écrit :
>
> Just considering my earlier make_enum comment further, The ultimate
> minimum for C++ is surely this?:
>
> traffic_light light{}; // whatever default.
> bool guaranteed_success = std::can_cast_enum<traffic_light>(x);
> if (guaranteed_success)
> light = reinterpret_cast<traffic_light>(x);
> // else error condition.
>
> It separates the validity check from the conversion and then you can
> build whatever on that, i.e. assign, atomic assign, etc.
> This seems more C++ like, but forgets C, which probably people feel is
> not a concern anyway.
> But other wise some special C and C++ syntax like
> can_cast_enum(traffic_light, x) would seem to cover both languages if
> that's desirable.
Yes, this is the good interface :)

I'm all for a proposal of this minimal interface, even if I would add a
enum_cast synonym of static_cast.
It is clear from the previous code that there is no problem with the
cast and using the same name would surely help

const traffic_light light = (std::can_enum_cast<traffic_light>(x))
? std::enum_cast<traffic_light>(x) :
throw_bad_enum_cast<traffic_light>(x);

Note that throw_bad_enum_cast is not proposed. It is only and example to
show the interface allows the use of safe conversions expressions.

We could see later what can be built on top of it and what could be on
the standard.

Vicente

m.ce...@gmail.com

unread,
Jan 4, 2017, 3:44:10 AM1/4/17
to ISO C++ Standard - Future Proposals
W dniu wtorek, 3 stycznia 2017 20:12:38 UTC+1 użytkownik Thiago Macieira napisał:
On terça-feira, 3 de janeiro de 2017 08:22:52 BRST m.ce...@gmail.com wrote:
> > How do you know whether a use of enum_cast is compile-time or whether it
> > is
> > runtime?
>
> I don't know and I don't care. The function should be constexpr to support
> both cases.

Well, I think people may care. A constexpr invocation has zero cost at
runtime, since it will have failed to convert by producing an error message,
or it will succeed by becoming a no-op (like static_cast, minus pointer
adjustment). But a non-constexpr invocation will have a runtime cost.

Well of course it will have runtime cost in non-constexpr invocation, the
whole point of this function is to perform validation at runtime.
Constexpr support is just an addition.
 
> What I'd like to hear now is whether anyone else would find such a
> functionality useful?
> Do you consider it a good idea to put it into C++ standard/library?

Maybe you should rename it to mean a check if the value is a valid enumerator
and drop the "cast" part.

I thought about it, but I see no use case for just a check.
In all the use cases I can think of user wants the cast after the check.

Do you have a example in mind where such a check without a cast later
would be useful?

m.ce...@gmail.com

unread,
Jan 4, 2017, 3:47:23 AM1/4/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com
W dniu środa, 4 stycznia 2017 00:48:25 UTC+1 użytkownik gmis...@gmail.com napisał:

Just considering my earlier make_enum comment further, The ultimate minimum for C++ is surely this?:

traffic_light light{}; // whatever default.
bool guaranteed_success = std::can_cast_enum<traffic_light>(x);
if (guaranteed_success)
 light = reinterpret_cast<traffic_light>(x);
// else error condition.

It separates the validity check from the conversion and then you can build whatever on that, i.e. assign, atomic assign, etc.
This seems more C++ like, but forgets C, which probably people feel is not a concern anyway.
But other wise some special C and C++ syntax like  can_cast_enum(traffic_light, x) would seem to cover both languages if that's desirable.


How would that can_cast_enum work in C without function overloading or templates?

Thiago Macieira

unread,
Jan 4, 2017, 9:30:12 AM1/4/17
to std-pr...@isocpp.org
On quarta-feira, 4 de janeiro de 2017 00:44:09 BRST m.ce...@gmail.com wrote:
> I thought about it, but I see no use case for just a check.
> In all the use cases I can think of user wants the cast after the check.
>
> Do you have a example in mind where such a check without a cast later
> would be useful?

The problem is that a cast that can fail needs a way out. dynamic_cast can
fail by returning nullptr (for pointers) or by throwing std::bad_cast (for
references).

So you need to find a way to report that failure.

And I don't want exceptions in my code.

Thiago Macieira

unread,
Jan 4, 2017, 9:33:45 AM1/4/17
to std-pr...@isocpp.org
On terça-feira, 3 de janeiro de 2017 19:43:33 BRST Nicol Bolas wrote:
> > Anything that is a compiler feature (a builtin or a core language
> > expression)
> > needs to be able to choose between std::optional,
> > std::experimental::optional
> > and std::__1::optional by just changing #includes.
>
> ... why? std::experimental::optional isn't part of the standard. And I have
> no idea what `std::__1::optional` is.

That's exactly the point: you don't know what the actual "optional" class's
full name is. Therefore, a language feature should not reference a strict
class name.

I know std::initializer_list has already broken this rule but there were
mitigating factors:
1) it's a class that only the compiler can create in the first place
2) it didn't exist prior to the feature that referenced it, so compiler and
library writers could cooperate, unlike optional
3) there aren't many ways to store an array of items, but there is a
discussion on how to store a type and its presence flag

Ditto for std::type_info.

Thiago Macieira

unread,
Jan 4, 2017, 9:35:27 AM1/4/17
to std-pr...@isocpp.org
On terça-feira, 3 de janeiro de 2017 15:48:25 BRST gmis...@gmail.com wrote:
> This seems more C++ like, but forgets C, which probably people feel is not
> a concern anyway.

It's not.

C enums are ints.

m.ce...@gmail.com

unread,
Jan 4, 2017, 10:20:11 AM1/4/17
to ISO C++ Standard - Future Proposals
W dniu środa, 4 stycznia 2017 15:30:12 UTC+1 użytkownik Thiago Macieira napisał:
On quarta-feira, 4 de janeiro de 2017 00:44:09 BRST m.ce...@gmail.com wrote:
> I thought about it, but I see no use case for just a check.
> In all the use cases I can think of user wants the cast after the check.
>
> Do you have a example in mind where such a check without a cast later
> would be useful?

The problem is that a cast that can fail needs a way out. dynamic_cast can
fail by returning nullptr (for pointers) or by throwing std::bad_cast (for
references).

So you need to find a way to report that failure.

And I don't want exceptions in my code.

That's exactly the reason why I have proposed to return optional<T>.

Why do you want to use some compiler invented type instead?
If enum_cast is a function in standard library, why can't it use another
type from same library?

Regards,
Maciej

Thiago Macieira

unread,
Jan 4, 2017, 11:16:17 AM1/4/17
to std-pr...@isocpp.org
On quarta-feira, 4 de janeiro de 2017 07:20:11 BRST m.ce...@gmail.com wrote:
> Why do you want to use some compiler invented type instead?
> If enum_cast is a function in standard library, why can't it use another
> type from same library?

Then it is a function in the standard library. That means it needs to be based
on something else to perform the check, like reflection.

Nicol Bolas

unread,
Jan 4, 2017, 11:33:28 AM1/4/17
to ISO C++ Standard - Future Proposals


On Wednesday, January 4, 2017 at 11:16:17 AM UTC-5, Thiago Macieira wrote:
On quarta-feira, 4 de janeiro de 2017 07:20:11 BRST m.ce...@gmail.com wrote:
> Why do you want to use some compiler invented type instead?
> If enum_cast is a function in standard library, why can't it use another
> type from same library?

Then it is a function in the standard library. That means it needs to be based
on something else to perform the check, like reflection.

There are plenty of functions in the standard library whose implementations rely on compiler-specific features. You wouldn't be able to implement most of the type traits without compiler intrinsic or some compiler-specific knowledge. You can't implement `type_info` or `initializer_list` without the compiler being involved. And so forth.

This would simply be one more compiler-based construct. Once we get compile-time reflection, implementations could become platform-neutral (though the compiler-equivalent would probably still get faster).

But how the function gets implemented ultimately has nothing to do with having it return `std::optional`.
 

Peter Koch Larsen

unread,
Jan 4, 2017, 2:28:43 PM1/4/17
to std-pr...@isocpp.org
For what it is worth, I have a small utility library for enumerations
(not to elegant, requiring a macro to define the machinery) where you
- among other stuff has two functions available that does what you
want

1) to_enum<Enum>(int x):
Returns the corresponding enum or an unspecified but valid value.
2) is_enum<Enum>(int x)
Returns true if x represents a valid enum.

Thus, If we have the definition

enum color
{
red = 1,
green = 200,
blue = 3
};

is_enum<color>(100) would return false and to_enum<color>(100) would
return either red, green or blue.

This approach is in my opinion better than forcing some
error-reporting mechanism such as returning an optional or throwing an
error.

/Peter
> --
> You received this message because you are subscribed to the Google Groups
> "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to std-proposal...@isocpp.org.
> To post to this group, send email to std-pr...@isocpp.org.
> To view this discussion on the web visit
> https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/8747ddc6-c416-44b9-a8a0-b3a00f835c05%40isocpp.org.

gmis...@gmail.com

unread,
Jan 10, 2017, 7:49:00 AM1/10/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com


On Tuesday, January 3, 2017 at 10:56:05 PM UTC+13, m.ce...@gmail.com wrote:
Hi,

I propose to add a enum_cast function that safely casts int value to a target enum type if int value represents a valid enumerator.

template <typename Enum, typename Int>
optional
<Enum> enum_cast(Int value);

requires
: is_enum<Enum>, is_integral<Int>

enum color
{
   red
= 1,
   green
= 200,
   blue
= 3
};

static_assert(enum_cast<color>(200u) == color::green);
static_assert(enum_cast<color>(3) == color::blue);
static_assert(enum_cast<color>(4) == nullopt);


This should work for both scoped and unscoped enums.
For opaque enums this would only check if value fits in underlying type.

Why do we want this:
 - validation of input data (e.g. coming from user or deserialization).
 - other?

I know this could be purely library extension if based on reflection, but since we don't know when we will get reflection,
and implementation based on reflection may not be optimal,
I think it should be implemented with compiler support (i.e. via __builtin_* intrinsic).

Regards,
Maciej


I was thinking some more about this proposal. I really think the functionality you suggest is a good idea.
I hope you proceed with a proposal. If you decide not to let me know.

I hope this post assists you with your proposal. My suggestions are for your proposal to look something like this:

In <utility> I think add:

bool template<typename E, typename V> is_enum( V ev )
{
 // compiler magic:
 // Calling this routine causes the compiler to generate or a call a routine
 // that returns true if the value ev matches one of the enum E's
 // values. otherwise false.
}

// Users call this routine to convert an integer to an enum.
They call this if they aren't sure about their conversion data
// and don't mind exceptions and want simple like std::to_string
// They might expect such a function to be present.
// Though to be fair I'm not sure we should encourage this?
// They should get good error messages.
template<typename E, typename V> E to_enum_or_throw(V ev)
{
 if (is_enum<E>(ev))
  return static_cast<E>(ev);
 throw make_bad_enum<E,V>(ev); 
}

template<typename E> class to_enum_result
{
public:
 E enum_value;
 bool is_valid;
};

// Call this if you can't use/afford exceptions. It seems optimal?
template<typename E, typename V> to_enum_result<E> to_enum( V ev ) noexcept
{
  if (is_enum<E, V>(ev))
    return to_enum_result<E>{true,static_cast<E>(ev)};
  return to_enum_result<E>{false, E{}};
}

// if is_valid returns false after to_enum, the user can use these routines to get good error
// messages / report how they wish when they wish:

// Some type that holds the largest enum value ever possible.
using std::any_enum_value = unspecified;

class bad_enum
{
public:
   std::any_any_enum_value bad_enum_value;
   std::string enum_type_name;
};
 
template<typename E, typename V> std::bad_enum make_bad_enum(V ev)
{
 // Use some compiler magic to get the name of the name of the enum type E.
 // Use it to construct a message like:
 // '{ev}' is not a legal value for an enum of type '{E}'
 return std::range_error(msg);
}


Note in all methods:
* constrain E to only be an enum or enum class type.
* constrain V to only accept integral types.

Observations
------------

* is_enum() is there for people who just need to check validity and no more.
and saves the user writing redudant boiler plate code that the compiler does better.
* to_enum_or_throw() is there for people who find simple works for them.
its name makes it clear that exceptions can happen.
* to_enum() is there for people who need performance and without exceptions.
* We don't depend on optional.
* Means <utility> isn't dependent on optional if we put it there.
* make_bad_enum avoids making bad_enum a template.
* make_bad_enum gives us good error messages and better than bad_optional_access.
* make_bad_enum means we can create an error class for reporting elsewhere - we don't have to throw it.
* make_bad_enum means we don't expose any get enum_type_name function the reflection people will reproduce.
* make_bad_enum gives people flexibility to throw when they are ready instead of forcing a throw as soon as conversion fails.
* Might play well with structured bindings.

Main choices:
* We could model this on optional i.e. include has_value etc. but it seems simpler not to. Either way is fine to me.
I'm not sure such safety is warranted and from_chars/to_chars is pretty simple too so I went that way.
* The caller has all the utilities here to build that or work with optional directly - whatever they need.
* I think being able to get good error messages on failure is important.
This tries to enable making decent errors available no matter what method or is used.
 i.e. include the enum type name and bad value in the error.
* I've used std::range_error as the exception type for failure but another may be appropriate.
bad_enum as an exception type contains the value that was bad and the type name that it failed to convert to.
This would allow people to extract the values to construct their own error messages (perhaps in other languages like French etc.).

If std::bad_enum is too much API, that could be dropped for just std::range_error or something as long a it contains
a full description of the error I think that's fine.

I'd like to know what you and the Committee's disposition towards these ideas and views. Hope this helps.

Thanks
GM

m.ce...@gmail.com

unread,
Jan 10, 2017, 10:31:08 AM1/10/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com
I'll try to create a first draft after I get some more comments w.r.t to exceptions.
For sure baseline API must be exception-free.
Do we really want to additionally support API that throws on error?
I'm rather inclined to leave it out - similarly as in to_chars/from_chars.
 

I hope this post assists you with your proposal. My suggestions are for your proposal to look something like this:

In <utility> I think add:

bool template<typename E, typename V> is_enum( V ev )
{
 // compiler magic:
 // Calling this routine causes the compiler to generate or a call a routine
 // that returns true if the value ev matches one of the enum E's
 // values. otherwise false.
}

'is_enum' name is already used in type_traits header.
Also is_enum seems redundant to me, since to_enum already reports
if convertion succeeded instead of:
if (is_enum<MyEnum>(139))

you could write:
if (to_enum<MyEnum>(139).is_valid)

or even:
if (to_enum<MyEnum>(139))
if we add explicit bool convertion operator for to_enum_result.



// Users call this routine to convert an integer to an enum.
They call this if they aren't sure about their conversion data
// and don't mind exceptions and want simple like std::to_string
// They might expect such a function to be present.
// Though to be fair I'm not sure we should encourage this?
// They should get good error messages.
template<typename E, typename V> E to_enum_or_throw(V ev)
{
 if (is_enum<E>(ev))
  return static_cast<E>(ev);
 throw make_bad_enum<E,V>(ev); 
}

template<typename E> class to_enum_result
{
public:
 E enum_value;
 bool is_valid;
};

// Call this if you can't use/afford exceptions. It seems optimal?
template<typename E, typename V> to_enum_result<E> to_enum( V ev ) noexcept
{
  if (is_enum<E, V>(ev))
    return to_enum_result<E>{true,static_cast<E>(ev)};
  return to_enum_result<E>{false, E{}};
}

// if is_valid returns false after to_enum, the user can use these routines to get good error
// messages / report how they wish when they wish:

As there is only one possible reason why the to_enum failed
(integer didn't match any enumerator of target enumeration type) 
I see no reason for following functions and classes.
As committee already stated their preference in from_chars/to_chars case, consider me convinced.
I will use 'to_enum_result' in draft.
Also I'll use 'to_enum' as function name to match 'to_chars' convention.
 
* The caller has all the utilities here to build that or work with optional directly - whatever they need.
* I think being able to get good error messages on failure is important.
This tries to enable making decent errors available no matter what method or is used.
 i.e. include the enum type name and bad value in the error. 
* I've used std::range_error as the exception type for failure but another may be appropriate.
bad_enum as an exception type contains the value that was bad and the type name that it failed to convert to.
This would allow people to extract the values to construct their own error messages (perhaps in other languages like French etc.).

If std::bad_enum is too much API, that could be dropped for just std::range_error or something as long a it contains
a full description of the error I think that's fine.

I'd like to know what you and the Committee's disposition towards these ideas and views. Hope this helps.


Thanks for your valuable input.

Regards,
Maciej
 

Thanks
GM

Matthew Woehlke

unread,
Jan 10, 2017, 10:49:34 AM1/10/17
to std-pr...@isocpp.org, m.ce...@gmail.com, gmis...@gmail.com
On 2017-01-10 10:31, m.ce...@gmail.com wrote:
> For sure baseline API must be exception-free.
> Do we really want to additionally support API that throws on error?

I *really* hope we can use something like std::expected; this is a
textbook use case for that, and makes it trivial to have a
throw-on-error API (just grab the value from the expected without
checking it first; this will turn around and throw if the value is not
there).

--
Matthew

Nicol Bolas

unread,
Jan 10, 2017, 11:20:12 AM1/10/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com, mwoehlk...@gmail.com

But the fact that it's throwing the wrong exception makes it useless from an exception handling perspective. If you want an API to throw, you want it to throw a meaningful exception type, not the same kind of exception that 20 other APIs throw.

So yes, we really do need to support a throwing API. I say we should have 3 interfaces:

* `can_enum_cast`: specifies whether the value is in an enumeration. This is where the compiler magic comes in, of course.
* `enum_cast`: Converts to the enumeration type, or throws if it cannot.
* `enum_cast_opt`: Returns an `optional<Enum>`.

The first allows you to build whatever tools you want. But we have default tools for C++'s standard mechanisms: throwing and `optional`.

Matthew Woehlke

unread,
Jan 10, 2017, 11:59:16 AM1/10/17
to std-pr...@isocpp.org
On 2017-01-10 11:20, Nicol Bolas wrote:
> On Tuesday, January 10, 2017 at 10:49:34 AM UTC-5, Matthew Woehlke wrote:
>> On 2017-01-10 10:31, m.ce...@gmail.com <javascript:> wrote:
>>> For sure baseline API must be exception-free.
>>> Do we really want to additionally support API that throws on error?
>>
>> I *really* hope we can use something like std::expected; this is a
>> textbook use case for that, and makes it trivial to have a
>> throw-on-error API (just grab the value from the expected without
>> checking it first; this will turn around and throw if the value is not
>> there).
>
> But the fact that it's throwing the wrong exception makes it useless from
> an exception handling perspective.

Huh? I'm talking about std::expected, not std::optional. If you didn't
get a value, you get whatever error/exception (preferably an exception
in this case, since it doesn't need more storage than the enum and thus
doesn't cost extra) the API stuffed in instead, which would presumably
be a std::range_error or std::bad_enum_cast or whatever. IOW, the *SAME*
exception that an API that throws right away would throw.

> * `enum_cast`: Converts to the enumeration type, or throws if it cannot.
> * `enum_cast_opt`: Returns an `optional<Enum>`.

These can be combined with no loss of function into one API that returns
a std::expected or similar.

--
Matthew

Peter Koch Larsen

unread,
Jan 10, 2017, 12:01:48 PM1/10/17
to std-pr...@isocpp.org
Nicols proposal is very similar to mine, except that I do not have an
enum_cast_opt. And I do not really see a reason to have this option.
I believe that noone would use that option. Why write

std::optional<Enum> e = enum_opt_cast<Enum>(x);
if (e)
{
normal processing
}
else
{
handle conversion failure
}

instead of

if (is_enum<Enum>(e))
{
Enum e = to_enum<Enum>(x);
normal processing
}
else
{
handle conversion failure
}
?
Things to consider when implementing:

What do you do if you have an enum with some same_valued elements
(enum E { ea = 0, eb = 0})? My implementation forbids this.
It could also be useful to have an ordinal position:

enum E
{
ea = 1000,
eb = -1000,
ec = 32
};

static_assert(to_ordinal<Enum>(ea) == 0,"??");
static_assert(to_ordinal<Enum>(eb) == 1,"??");
static_assert(to_ordinal<Enum>(ec) == 2,"??");

I have found uses for this in my code.

/Peter
> --
> You received this message because you are subscribed to the Google Groups
> "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to std-proposal...@isocpp.org.
> To post to this group, send email to std-pr...@isocpp.org.
> To view this discussion on the web visit
> https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/e85f0d46-f244-42fe-a276-b4e18b3d627f%40isocpp.org.

Matthew Woehlke

unread,
Jan 10, 2017, 1:31:11 PM1/10/17
to std-pr...@isocpp.org
On 2017-01-10 12:01, Peter Koch Larsen wrote:
> What do you do if you have an enum with some same_valued elements
> (enum E { ea = 0, eb = 0})? My implementation forbids this.

Why?

E x = ea;
assert(x == eb); // will succeed

Such a prohibition makes some sense when converting an enum to a string
(or to its ordinal), but there is no problem when converting an int to
an enum (or a string to an enum, for that matter).

--
Matthew

Vicente J. Botet Escriba

unread,
Jan 10, 2017, 1:49:05 PM1/10/17
to std-pr...@isocpp.org, m.ce...@gmail.com, gmis...@gmail.com
Le 10/01/2017 à 16:31, m.ce...@gmail.com a écrit :

W dniu wtorek, 10 stycznia 2017 13:49:00 UTC+1 użytkownik gmis...@gmail.com napisał:


On Tuesday, January 3, 2017 at 10:56:05 PM UTC+13, m.ce...@gmail.com wrote:
Hi,


I'll try to create a first draft after I get some more comments w.r.t to exceptions.
For sure baseline API must be exception-free.
Do we really want to additionally support API that throws on error?
I'm rather inclined to leave it out - similarly as in to_chars/from_chars.
 

I hope this post assists you with your proposal. My suggestions are for your proposal to look something like this:

In <utility> I think add:

bool template<typename E, typename V> is_enum( V ev )
{
 // compiler magic:
 // Calling this routine causes the compiler to generate or a call a routine
 // that returns true if the value ev matches one of the enum E's
 // values. otherwise false.
}

'is_enum' name is already used in type_traits header.
Also is_enum seems redundant to me, since to_enum already reports
if convertion succeeded instead of:
if (is_enum<MyEnum>(139))

you could write:
if (to_enum<MyEnum>(139).is_valid)

or even:
if (to_enum<MyEnum>(139))
if we add explicit bool convertion operator for to_enum_result.


I believe we need two checking functions:
* is_enumerator : checks if the explicit conversion from the integer is one of the explicit enumerators
* is_in_enum_range: checks if the integer is in the range of valid values. This is the precondition of the static_cast.

IIUC when the underlying type is explicit, the range of values are the range of the underlying type. However when the underlying type is implicit the range goes from the min to the max of the values of the enumerators.

Until we don't have an enum that restrict the validity of the values to his explicit enumerators, the range check seems to be useful at least to avoid UB.

It is easy to build then whatever we want on top of these checks.
to_enum: throw exception
to_enum_or
try_to_enum: return optional or whatever is more appropriated.

Having good names for both check variants will surely take some time.

Note that we don't know yet if expected<T,E> will throw bad_expected or E.

I believe we can start by a proposal that includes only the checks and surface the possible usage.

Vicente

P.S. Both checks can be built on top of the static reflection interface.

Vicente J. Botet Escriba

unread,
Jan 10, 2017, 2:00:31 PM1/10/17
to std-pr...@isocpp.org
Le 10/01/2017 à 18:01, Peter Koch Larsen a écrit :
> Nicols proposal is very similar to mine, except that I do not have an
> enum_cast_opt. And I do not really see a reason to have this option.
> I believe that noone would use that option. Why write
>
> std::optional<Enum> e = enum_opt_cast<Enum>(x);
Because optional is a monad and you can do much more than checking for
whether it is present?
Anyway, all these functions could be defined in top of the checks.
> if (e)
> {
> normal processing
> }
> else
> {
> handle conversion failure
> }
>
> instead of
>
> if (is_enum<Enum>(e))
> {
> Enum e = to_enum<Enum>(x);
> normal processing
> }
> else
> {
> handle conversion failure
> }
> ?
> Things to consider when implementing:
>
> What do you do if you have an enum with some same_valued elements
> (enum E { ea = 0, eb = 0})? My implementation forbids this.
What is the advantage to forbidding it.
> It could also be useful to have an ordinal position:
>
> enum E
> {
> ea = 1000,
> eb = -1000,
> ec = 32
> };
>
> static_assert(to_ordinal<Enum>(ea) == 0,"??");
> static_assert(to_ordinal<Enum>(eb) == 1,"??");
> static_assert(to_ordinal<Enum>(ec) == 2,"??");
I'm working on a proposal on ordinal types. We can consider that an enum
having all the enumerators different is an ordinal type if we consider
only these enumerators are valid. We could as well consider that any
Integral type is an ordinal type.
This proposal will include also the value associated to a position and
on top of that ordinal_array, ordinal_set and ordinal_range.

https://github.com/viboes/std-make/tree/master/include/experimental/fundamental/v3/ordinal

Vicente

Nicol Bolas

unread,
Jan 10, 2017, 2:09:42 PM1/10/17
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Tuesday, January 10, 2017 at 11:59:16 AM UTC-5, Matthew Woehlke wrote:
On 2017-01-10 11:20, Nicol Bolas wrote:
> On Tuesday, January 10, 2017 at 10:49:34 AM UTC-5, Matthew Woehlke wrote:
>> On 2017-01-10 10:31, m.ce...@gmail.com <javascript:> wrote:
>>> For sure baseline API must be exception-free.
>>> Do we really want to additionally support API that throws on error?
>>
>> I *really* hope we can use something like std::expected; this is a
>> textbook use case for that, and makes it trivial to have a
>> throw-on-error API (just grab the value from the expected without
>> checking it first; this will turn around and throw if the value is not
>> there).
>
> But the fact that it's throwing the wrong exception makes it useless from
> an exception handling perspective.

Huh? I'm talking about std::expected, not std::optional. If you didn't
get a value, you get whatever error/exception (preferably an exception
in this case, since it doesn't need more storage than the enum and thus
doesn't cost extra) the API stuffed in instead, which would presumably
be a std::range_error or std::bad_enum_cast or whatever. IOW, the *SAME*
exception that an API that throws right away would throw.

Actually, you can't do that. Well, not in P0323 `expected`. It has a specific requirement that `E` is no-throw move constructible (in order to ensure never-empty without bloat). But `std::range_error` has no such guarantee on its move constructor. Also, it throws `bad_expected_access<E>`, not `E` itself.

Even ignoring that, you want the API to return `std::expected<Enum, std::range_error>`. Well, `std::range_error` derives from `std::runtime_error`. And that internally stores a string. Not necessarily `std::string`, but it stores a sequence of characters it dynamically allocates, along with the means to delete it. And whatever that storage may be, it's almost certainly bigger than `sizeof(Enum)`.

Which now means that your return type is needlessly bloated. If you had returned `std::optional<Enum>`, there would be no bloat.

Generally speaking, you should never use the actual exception type that you would have otherwise thrown as the non-expected value in an `expected`. `expected` errors are intended to be lightweight: error codes and the like. Not things that own memory and so forth. By contrast, exceptions can be quite heavy.

Nicol Bolas

unread,
Jan 10, 2017, 2:30:40 PM1/10/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com

Right, but `is_enumerator` is a functional superset of `is_in_enum_range`. Do people really need to ask only if a value is in the range of an enumerator?

Vicente J. Botet Escriba

unread,
Jan 10, 2017, 5:26:11 PM1/10/17
to std-pr...@isocpp.org, m.ce...@gmail.com, gmis...@gmail.com
I don't follow you. You surely wanted to say subset

The standard says in 7.2/8 :
  1. 8  For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, for an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two’s complement representation and 0 for a ones’ complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin| − K, |emax|) and equal to 2M 1, where M is a non-negative integer. bmin is zero if emin is non-negative and (bmax + K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M,1) if bmin is zero and M + 1 otherwise. It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.


For me enumerators are any one of the named enum values. This set is a subset  not a superset of the range of valid values.

As I said, until we don't have enums that consists only of the enumerators, there would be always the need to check if a value is a valid value for the enumeration.

We could define a class that accepts only the enumerators as valid values, but the language enums can accept more values.
This is way I believe that the two checks are needed.

I don't know why the new C++11 enum with an explicit underlying type have a different range of valid values.
I'll be interested in knowing the rationale.

Vicente

Nicol Bolas

unread,
Jan 10, 2017, 6:06:56 PM1/10/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com

Yes, I did. My mistake.
 

The standard says in 7.2/8 :
  1. 8  For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, for an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two’s complement representation and 0 for a ones’ complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin| − K, |emax|) and equal to 2M 1, where M is a non-negative integer. bmin is zero if emin is non-negative and (bmax + K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M,1) if bmin is zero and M + 1 otherwise. It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.


For me enumerators are any one of the named enum values. This set is a subset  not a superset of the range of valid values.

As I said, until we don't have enums that consists only of the enumerators, there would be always the need to check if a value is a valid value for the enumeration.
 
We could define a class that accepts only the enumerators as valid values, but the language enums can accept more values.
This is way I believe that the two checks are needed.

Here's the thing.

There are enumerations that have a fixed underlying type (`enum class`es use `int` by default), and there are enumerations that have an implied underlying type (ie: non-`class` enums without a specified type).

The question I have to ask is this: if you have an integer, why do you need to know if it will fit within the range of an enum with an implied underlying type? What problem are you trying to solve? What code are you trying to write?

If the enum has a fixed underlying type, then you might need to know the range because you're using the enum as an ad-hoc strongly typed integer. I personally despise this obvious abuse of a language feature, but C++17 has effectively canonized it, so there it is. Alternatively, you may be using that enum as a bitfield.

The thing is, that is a solved problem: get the underlying type with `std::underlying_type_t<E>`. That, and its corresponding `numeric_limits` will tell you everything you need to know about the range of that enumeration.

But if the enum has an implied underlying type, then why would you need to know if a particular integer (which does not match any enumerator) is within the valid range for that enum? What are you trying to do that you need to do this?

If you don't have a problem to be solved with such a function, then there's really no point in adding one.

I don't know why the new C++11 enum with an explicit underlying type have a different range of valid values.
I'll be interested in knowing the rationale.

Because enums are integers. That's the rationale.

Peter Koch Larsen

unread,
Jan 11, 2017, 2:58:05 AM1/11/17
to std-pr...@isocpp.org
This is exactly my problem. I partially use my library in to do this
stuff, and it could be confusing for the user if you serialize as ea
and get eb back. I also have a bitset class (enum_bitset<Enum>), where
I as a template parameter define if i set the bit depending on the
underlying enums ordinal values or by its underlying integral value.
In neither case do I see any use of having two enums with the same
value.
Also, I do not see a reason in have two enums with the same value.
Before my library, I would have two underlying enums with the same
value in two situations: To have a count (e.g. enum color {
red,green,blue, color_count = blue} or to name bit-sets enum state {
created = 1, received = 2, sent = 4, active = created}). These reasons
are both gone now.

/Peter


>
> --
> Matthew
>

gmis...@gmail.com

unread,
Jan 11, 2017, 5:40:40 AM1/11/17
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
My suggestion I talked about a  make_bad_enum function which a template function that you passed the type of the enum that you thought was bad and a value (not an enum) that you had that you were asserting was 'bad' - i.e. you had discovered it was not convertible to an enum.
And I was suggesting that the make_bad_enum function had compiler magic that new how to turn your enum type e.g. enum my::traffic_light {whatever=whatever} into a string/char array "my:;traffic_light".

It would then create a string of the info an give you an exception object back that you could then throw or do whatever with.
so given namespace my { enum class traffic_light { stop, careful, go }; } }
make_enum<traffic_light>(3) would produce an exception where what() would return an array with
'3 is not a valid value for my:;traffic_light'.

I think this is desirable to just having either an std::bad_optional_access with what string exactly in?

std::range_error was ok with me as that could be constructed with a string and the type more reflected the problem.

What's wrong with this in your opinion?

A bad_enum class would have allowed an internal char buffer of fixed size to be used because we know what length string worst case we are storing here if that was the problem.

But what am I missing?
Thanks

Matthew Woehlke

unread,
Jan 11, 2017, 10:23:56 AM1/11/17
to std-pr...@isocpp.org
On 2017-01-11 02:58, Peter Koch Larsen wrote:
> On Tue, Jan 10, 2017 at 7:31 PM, Matthew Woehlke wrote:
>> On 2017-01-10 12:01, Peter Koch Larsen wrote:
>>> What do you do if you have an enum with some same_valued elements
>>> (enum E { ea = 0, eb = 0})? My implementation forbids this.
>>
>> Why?
>>
>> E x = ea;
>> assert(x == eb); // will succeed
>>
>> Such a prohibition makes some sense when converting an enum to a string
>> (or to its ordinal), but there is no problem when converting an int to
>> an enum (or a string to an enum, for that matter).
>
> This is exactly my problem. I partially use my library in to do this
> stuff, and it could be confusing for the user if you serialize as ea
> and get eb back.

...but you will *also* get `ea` back. The values `ea` and `eb` are not
distinguishable when represented as either the enum nor as an integer
(via value cast). They are only distinguishable as strings or ordinals.

If that isn't what you want, don't give your enum multiple identifiers
with the same value in the first place.

> I also have a bitset class (enum_bitset<Enum>), where
> I as a template parameter define if i set the bit depending on the
> underlying enums ordinal values or by its underlying integral value.
> In neither case do I see any use of having two enums with the same
> value.
> Also, I do not see a reason in have two enums with the same value.

So just *don't do that*. But don't artificially nerf your proposed
feature for people that *do* find that useful.

In your above example, the integer 0 is a member of the set of valid
values of `E`. The cast should succeed. Remember, you aren't changing
the value representation, you're just changing the *type*.

--
Matthew

Nicol Bolas

unread,
Jan 11, 2017, 12:16:58 PM1/11/17
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com, gmis...@gmail.com

Sure, but... I'm not arguing against what you suggested. Indeed, I was specifically arguing against the idea that generic exceptions (whether `bad_optional_access` or ` bad_expected_access<E>`) were a good idea.

Are you sure you intend to be replying to me?

std::range_error was ok with me as that could be constructed with a string and the type more reflected the problem.

What's wrong with this in your opinion?

I see no reason to use a generic exception when an exception type specific to enumeration casting could be employed instead. It's ultimately more descriptive and obvious what's going on.

A bad_enum class would have allowed an internal char buffer of fixed size to be used because we know what length string worst case we are storing here if that was the problem.

How? An enumeration can be a member of any number of namespaces and/or classes. Thus its name can be quite long. While any particular implementation could use its internal compiler limits to give it a maximum length, that length would be pretty huge, tens if not hundreds of kilobytes long.

Better to dynamically allocate it than to throw such a gargantuan object around.

Vicente J. Botet Escriba

unread,
Jan 11, 2017, 1:52:11 PM1/11/17
to std-pr...@isocpp.org, m.ce...@gmail.com, gmis...@gmail.com
What is important is the range of values.


The question I have to ask is this: if you have an integer, why do you need to know if it will fit within the range of an enum with an implied underlying type?
Because initializing the enum with an integer out of range is UB.

What problem are you trying to solve? What code are you trying to write?

If the enum has a fixed underlying type, then you might need to know the range because you're using the enum as an ad-hoc strongly typed integer.
Right.

I personally despise this obvious abuse of a language feature, but C++17 has effectively canonized it, so there it is. Alternatively, you may be using that enum as a bitfield.

The thing is, that is a solved problem: get the underlying type with `std::underlying_type_t<E>`. That, and its corresponding `numeric_limits` will tell you everything you need to know about the range of that enumeration.
I don't want to solve problems that are already solved. We don't need any modification on the compiler to solve this case, but if it solve the more dificult case it could  solve this as well.


But if the enum has an implied underlying type, then why would you need to know if a particular integer (which does not match any enumerator) is within the valid range for that enum?
Because this is legal. I can assign it a value on the specified range. That's all. If I want my code to be outside the UB world I should be able to check on the conditions. I can of course do it for each particular case, but what we are talking of here is about what the compiler could do for us in a generic way.

What are you trying to do that you need to do this?
This is not a use case I would write myself, but I've see it a lot of times. When you use enums as flags of an bitset
enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

The valid enumerators  don't correspond to the valid range, that in this case is any value between 0 and 8.


If you don't have a problem to be solved with such a function, then there's really no point in adding one.
I was sure you will ask and say this ;-)


I don't know why the new C++11 enum with an explicit underlying type have a different range of valid values.
I'll be interested in knowing the rationale.

Because enums are integers. That's the rationale.
I believe that you didn't understood my question. Let me see with an example.
What is the difference between

    enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

and

    enum class Y : unsigned char { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};
?

X has a valid range 0..N, while Y has a valid range 0..255.

Why do we need this difference? Why forcing the underlying type changes the range of valid values?

Vicente

Peter Koch Larsen

unread,
Jan 11, 2017, 2:23:11 PM1/11/17
to std-pr...@isocpp.org
On Wed, Jan 11, 2017 at 4:23 PM, Matthew Woehlke
<mwoehlk...@gmail.com> wrote:
> On 2017-01-11 02:58, Peter Koch Larsen wrote:
>> On Tue, Jan 10, 2017 at 7:31 PM, Matthew Woehlke wrote:
>>> On 2017-01-10 12:01, Peter Koch Larsen wrote:
>>>> What do you do if you have an enum with some same_valued elements
>>>> (enum E { ea = 0, eb = 0})? My implementation forbids this.
>>>
>>> Why?
>>>
>>> E x = ea;
>>> assert(x == eb); // will succeed
>>>
>>> Such a prohibition makes some sense when converting an enum to a string
>>> (or to its ordinal), but there is no problem when converting an int to
>>> an enum (or a string to an enum, for that matter).
>>
>> This is exactly my problem. I partially use my library in to do this
>> stuff, and it could be confusing for the user if you serialize as ea
>> and get eb back.
>
> ...but you will *also* get `ea` back. The values `ea` and `eb` are not
> distinguishable when represented as either the enum nor as an integer
> (via value cast). They are only distinguishable as strings or ordinals.
>
And?

> If that isn't what you want, don't give your enum multiple identifiers
> with the same value in the first place.

I don't. ;-)

>
>
> So just *don't do that*. But don't artificially nerf your proposed
> feature for people that *do* find that useful.

Well, I have not proposed anything here - perhaps you should reread this thread?
I simply gave an outline as to how I implemented a feature that was
close to what is proposed, and tried to bring to attention some of
the open questions I experienced while doing that.
Personally, I would prefer to not standardize enum_cast. My preference
would be to wait for reflection to be implemented. At that point it
will probably just be a simple library function.

/Peter

gmis...@gmail.com

unread,
Jan 11, 2017, 3:21:42 PM1/11/17
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com, gmis...@gmail.com
It would then create a string of the info an give you an exception object back that you could then throw or do whatever with.
so given namespace my { enum class traffic_light { stop, careful, go }; } }
make_enum<traffic_light>(3) would produce an exception where what() would return an array with
'3 is not a valid value for my:;traffic_light'.

I think this is desirable to just having either an std::bad_optional_access with what string exactly in?

Sure, but... I'm not arguing against what you suggested. Indeed, I was specifically arguing against the idea that generic exceptions (whether `bad_optional_access` or ` bad_expected_access<E>`) were a good idea.

Are you sure you intend to be replying to me?

Yes I intended to reply to you, but you are right it would have been better to reply to someone in less agreement
for what I want to know.
 

std::range_error was ok with me as that could be constructed with a string and the type more reflected the problem.

What's wrong with this in your opinion?

I see no reason to use a generic exception when an exception type specific to enumeration casting could be employed instead. It's ultimately more descriptive and obvious what's going on.

A bad_enum class would have allowed an internal char buffer of fixed size to be used because we know what length string worst case we are storing here if that was the problem.

How? An enumeration can be a member of any number of namespaces and/or classes. Thus its name can be quite long. While any particular implementation could use its internal compiler limits to give it a maximum length, that length would be pretty huge, tens if not hundreds of kilobytes long.

Better to dynamically allocate it than to throw such a gargantuan object around.

If it had to bet truncated it wouldn't be the end of the world (one hopes!),
I was thinking about avoiding dynamic allocation, but you are probably right that's a bad idea anyway.

gmis...@gmail.com

unread,
Jan 11, 2017, 3:49:57 PM1/11/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com

What is important is the range of values.

The question I have to ask is this: if you have an integer, why do you need to know if it will fit within the range of an enum with an implied underlying type?
Because initializing the enum with an integer out of range is UB.

In my earlier post where I described at length how I thought the API should be, was there UB in that and if so where?
I'm trying to understand where your UB comments are directed as I'm not seeing where this happens.
 
What problem are you trying to solve? What code are you trying to write?

If the enum has a fixed underlying type, then you might need to know the range because you're using the enum as an ad-hoc strongly typed integer.
Right.
I personally despise this obvious abuse of a language feature, but C++17 has effectively canonized it, so there it is. Alternatively, you may be using that enum as a bitfield.

The thing is, that is a solved problem: get the underlying type with `std::underlying_type_t<E>`. That, and its corresponding `numeric_limits` will tell you everything you need to know about the range of that enumeration.
I don't want to solve problems that are already solved. We don't need any modification on the compiler to solve this case, but if it solve the more dificult case it could  solve this as well.

What are you both referring to here? I assumed modifying the compiler was a requirement to implement is_enum?
Or maybe you know is_enum can already be implemented through meta programming without a compiler change?
Or maybe you are talking about modifying the language (so still a compiler change) but so is_enum can implement this feature?


But if the enum has an implied underlying type, then why would you need to know if a particular integer (which does not match any enumerator) is within the valid range for that enum?
Because this is legal. I can assign it a value on the specified range. That's all. If I want my code to be outside the UB world I should be able to check on the conditions. I can of course do it for each particular case, but what we are talking of here is about what the compiler could do for us in a generic way.

The compiler is generating the code so where does generic come into this?

What are you trying to do that you need to do this?
This is not a use case I would write myself, but I've see it a lot of times. When you use enums as flags of an bitset
enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

The valid enumerators  don't correspond to the valid range, that in this case is any value between 0 and 8.

If you don't have a problem to be solved with such a function, then there's really no point in adding one.
I was sure you will ask and say this ;-)

I don't know why the new C++11 enum with an explicit underlying type have a different range of valid values.
I'll be interested in knowing the rationale.

Because enums are integers. That's the rationale.
I believe that you didn't understood my question. Let me see with an example.
What is the difference between

    enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

and

    enum class Y : unsigned char { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};
?

X has a valid range 0..N, while Y has a valid range 0..255.

Why do we need this difference? Why forcing the underlying type changes the range of valid values?

I had std::any_enum_value in my earlier post and make_bad_enum knew the type of the enum.
The idea was that those two pieces of information allowed a value to be constructed that could express any enum as intended.
I'm raising that in case it's helpful to this conversation here but it might not be because I can't quite follow what problem is being solved here?
 

Vicente

Nicol Bolas

unread,
Jan 11, 2017, 4:21:15 PM1/11/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com

You're misunderstanding my question.

The situation you describe is one where:

1. You have an integer of arbitrary origin.

2. You want to convert it to an enum type.

3. That integer does not match one of the enumerators in that type.

4. The enum type does not have a fixed underlying type.

What goal are you trying to achieve with all this? Or more to the point, why are you incapable of simply giving the enum an underlying type and thus making the question moot?
What problem are you trying to solve? What code are you trying to write?

If the enum has a fixed underlying type, then you might need to know the range because you're using the enum as an ad-hoc strongly typed integer.
Right.
I personally despise this obvious abuse of a language feature, but C++17 has effectively canonized it, so there it is. Alternatively, you may be using that enum as a bitfield.

The thing is, that is a solved problem: get the underlying type with `std::underlying_type_t<E>`. That, and its corresponding `numeric_limits` will tell you everything you need to know about the range of that enumeration.
I don't want to solve problems that are already solved. We don't need any modification on the compiler to solve this case, but if it solve the more dificult case it could  solve this as well.

But if the enum has an implied underlying type, then why would you need to know if a particular integer (which does not match any enumerator) is within the valid range for that enum?
Because this is legal. I can assign it a value on the specified range. That's all. If I want my code to be outside the UB world I should be able to check on the conditions. I can of course do it for each particular case, but what we are talking of here is about what the compiler could do for us in a generic way.
What are you trying to do that you need to do this?
This is not a use case I would write myself, but I've see it a lot of times. When you use enums as flags of an bitset
enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

The valid enumerators  don't correspond to the valid range, that in this case is any value between 0 and 8.

But we can already answer that question. `X` is an `enum class`. As such, it always has a fixed underlying type. If you don't specify one, then it shall be `int`, and therefore `X` can legally assume any `int` value.

So the `std::underlying_type`-based solution will work fine.

It should also be noted that the standard-specified range guarantees the ability to use an enum with an implied underlying type as a bitfield. That is, if you perform bitwise operations with the enumerators, the results are guaranteed to fit in the range. So you have nothing to worry about for that use case.
If you don't have a problem to be solved with such a function, then there's really no point in adding one.
I was sure you will ask and say this ;-)

I don't know why the new C++11 enum with an explicit underlying type have a different range of valid values.
I'll be interested in knowing the rationale.

Because enums are integers. That's the rationale.
I believe that you didn't understood my question. Let me see with an example.
What is the difference between

    enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

and

    enum class Y : unsigned char { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};
?

X has a valid range 0..N, while Y has a valid range 0..255.

Why do we need this difference? Why forcing the underlying type changes the range of valid values?

In that case, both have a forced underlying type, as I pointed out above. So the reason for the differing ranges of values is obvious.

Now, let's assume you have revised your example to not use `enum class`. `enum X` does not have a fixed underlying type, so its range is determined by its enumerators. The reason that is different is because that's how it always was before, and there's terribly little reason to change it.

Nicol Bolas

unread,
Jan 11, 2017, 4:25:48 PM1/11/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com
On Wednesday, January 11, 2017 at 3:49:57 PM UTC-5, gmis...@gmail.com wrote:
I personally despise this obvious abuse of a language feature, but C++17 has effectively canonized it, so there it is. Alternatively, you may be using that enum as a bitfield.

The thing is, that is a solved problem: get the underlying type with `std::underlying_type_t<E>`. That, and its corresponding `numeric_limits` will tell you everything you need to know about the range of that enumeration.
I don't want to solve problems that are already solved. We don't need any modification on the compiler to solve this case, but if it solve the more dificult case it could  solve this as well.

What are you both referring to here? I assumed modifying the compiler was a requirement to implement is_enum?
Or maybe you know is_enum can already be implemented through meta programming without a compiler change?
Or maybe you are talking about modifying the language (so still a compiler change) but so is_enum can implement this feature?

This conversation is separate from most of the rest of the thread.

The standard defines the difference between the range of values that are legal for an enum type and the enumerators for that enum. `is_enum` answers the latter question: is the integer one of the enumerators? What Vicente wants is an answer to the former question: is the integer's value within the range of the enumeration?

The reason it's non-trivial to implement yourself is because, for enums with an implied underlying type, the valid range of the enumeration is defined by the values of the enumerators, rather than the compiler-selected underlying type. There's no way to compute that require reflection or compiler gymnastics.

My main point of contention for the feature is why someone needs to ask it. I don't feel Vicente has given sufficient justification for it.

Vicente J. Botet Escriba

unread,
Jan 12, 2017, 2:28:00 AM1/12/17
to std-pr...@isocpp.org, m.ce...@gmail.com, gmis...@gmail.com
Le 11/01/2017 à 22:21, Nicol Bolas a écrit :
On Wednesday, January 11, 2017 at 1:52:11 PM UTC-5, Vicente J. Botet Escriba wrote:
Le 11/01/2017 à 00:06, Nicol Bolas a écrit :
On Tuesday, January 10, 2017 at 5:26:11 PM UTC-5, Vicente J. Botet Escriba wrote:
Le 10/01/2017 à 20:30, Nicol Bolas a écrit :
On Tuesday, January 10, 2017 at 1:49:05 PM UTC-5, Vicente J. Botet Escriba wrote:
Le 10/01/2017 à 16:31, m.ce...@gmail.com a écrit :


I believe we need two checking functions:
* is_enumerator : checks if the explicit conversion from the integer is one of the explicit enumerators
* is_in_enum_range: checks if the integer is in the range of valid values. This is the precondition of the static_cast.

IIUC when the underlying type is explicit, the range of values are the range of the underlying type. However when the underlying type is implicit the range goes from the min to the max of the values of the enumerators.

Right, but `is_enumerator` is a functional superset of `is_in_enum_range`. Do people really need to ask only if a value is in the range of an enumerator?

 
The standard says in 7.2/8 :
  1. 8  For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, for an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two’s complement representation and 0 for a ones’ complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin| − K, |emax|) and equal to 2M 1, where M is a non-negative integer. bmin is zero if emin is non-negative and (bmax + K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M,1) if bmin is zero and M + 1 otherwise. It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.


For me enumerators are any one of the named enum values. This set is a subset  not a superset of the range of valid values.

As I said, until we don't have enums that consists only of the enumerators, there would be always the need to check if a value is a valid value for the enumeration.
 
We could define a class that accepts only the enumerators as valid values, but the language enums can accept more values.
This is way I believe that the two checks are needed.

Here's the thing.

There are enumerations that have a fixed underlying type (`enum class`es use `int` by default), and there are enumerations that have an implied underlying type (ie: non-`class` enums without a specified type).
What is important is the range of values.

The question I have to ask is this: if you have an integer, why do you need to know if it will fit within the range of an enum with an implied underlying type?
Because initializing the enum with an integer out of range is UB.

You're misunderstanding my question.

The situation you describe is one where:

1. You have an integer of arbitrary origin.

2. You want to convert it to an enum type.

3. That integer does not match one of the enumerators in that type.

4. The enum type does not have a fixed underlying type.

You have described very well the context.

What goal are you trying to achieve with all this? Or more to the point, why are you incapable of simply giving the enum an underlying type and thus making the question moot?
Because the enum is declared in a 3pp library in C++98? or a common part that is shared by an application using C++98 and another using C++2x?

What problem are you trying to solve? What code are you trying to write?

If the enum has a fixed underlying type, then you might need to know the range because you're using the enum as an ad-hoc strongly typed integer.
Right.
I personally despise this obvious abuse of a language feature, but C++17 has effectively canonized it, so there it is. Alternatively, you may be using that enum as a bitfield.

The thing is, that is a solved problem: get the underlying type with `std::underlying_type_t<E>`. That, and its corresponding `numeric_limits` will tell you everything you need to know about the range of that enumeration.
I don't want to solve problems that are already solved. We don't need any modification on the compiler to solve this case, but if it solve the more dificult case it could  solve this as well.

But if the enum has an implied underlying type, then why would you need to know if a particular integer (which does not match any enumerator) is within the valid range for that enum?
Because this is legal. I can assign it a value on the specified range. That's all. If I want my code to be outside the UB world I should be able to check on the conditions. I can of course do it for each particular case, but what we are talking of here is about what the compiler could do for us in a generic way.
What are you trying to do that you need to do this?
This is not a use case I would write myself, but I've see it a lot of times. When you use enums as flags of an bitset
enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

The valid enumerators  don't correspond to the valid range, that in this case is any value between 0 and 8.

But we can already answer that question. `X` is an `enum class`. As such, it always has a fixed underlying type. If you don't specify one, then it shall be `int`, and therefore `X` can legally assume any `int` value.
From the text of the standard I send before, the range is not the range of int. Could you point me from where are you concluding this?
Is the same if the enum are not scoped?


So the `std::underlying_type`-based solution will work fine.

It should also be noted that the standard-specified range guarantees the ability to use an enum with an implied underlying type as a bitfield. That is, if you perform bitwise operations with the enumerators, the results are guaranteed to fit in the range. So you have nothing to worry about for that use case.
If you don't have a problem to be solved with such a function, then there's really no point in adding one.
I was sure you will ask and say this ;-)

I don't know why the new C++11 enum with an explicit underlying type have a different range of valid values.
I'll be interested in knowing the rationale.

Because enums are integers. That's the rationale.
I believe that you didn't understood my question. Let me see with an example.
What is the difference between

    enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

and

    enum class Y : unsigned char { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};
?

X has a valid range 0..N, while Y has a valid range 0..255.

Why do we need this difference? Why forcing the underlying type changes the range of valid values?

In that case, both have a forced underlying type, as I pointed out above. So the reason for the differing ranges of values is obvious.

Now, let's assume you have revised your example to not use `enum class`. `enum X` does not have a fixed underlying type, so its range is determined by its enumerators. The reason that is different is because that's how it always was before, and there's terribly little reason to change it.
I'm not questioning the old C++98 behavior but the new C++11 behavior (if we can say new for C++11)

Vicente

Vicente J. Botet Escriba

unread,
Jan 12, 2017, 2:45:08 AM1/12/17
to std-pr...@isocpp.org, m.ce...@gmail.com, gmis...@gmail.com
Le 11/01/2017 à 21:49, gmis...@gmail.com a écrit :

What is important is the range of values.

The question I have to ask is this: if you have an integer, why do you need to know if it will fit within the range of an enum with an implied underlying type?
Because initializing the enum with an integer out of range is UB.

In my earlier post where I described at length how I thought the API should be, was there UB in that and if so where?
I'm trying to understand where your UB comments are directed as I'm not seeing where this happens.
The interface you have proposed doesn't introduce any UB, it avoid it, because the test is over a subset of the valid values for an enumeration. The UB is defined in standard. See the paragraph I mentioned. What I'm saying is that we need a function that checks also exactly for the valid values. Is that so complex to understand.


 
What problem are you trying to solve? What code are you trying to write?

If the enum has a fixed underlying type, then you might need to know the range because you're using the enum as an ad-hoc strongly typed integer.
Right.
I personally despise this obvious abuse of a language feature, but C++17 has effectively canonized it, so there it is. Alternatively, you may be using that enum as a bitfield.

The thing is, that is a solved problem: get the underlying type with `std::underlying_type_t<E>`. That, and its corresponding `numeric_limits` will tell you everything you need to know about the range of that enumeration.
I don't want to solve problems that are already solved. We don't need any modification on the compiler to solve this case, but if it solve the more dificult case it could  solve this as well.

What are you both referring to here?
We are talking here of is_in _enum_rang (or is_valid_enum) by opposition to is_enumerator (your is_enum).
is_valid_enum can be implemented easily with the current meta-programming when the underlying type is explict.

I assumed modifying the compiler was a requirement to implement is_enum?
I would say that it is easier to maintain if the compiler generates the function. Anyone can define such a function for each enum.

Or maybe you know is_enum can already be implemented through meta programming without a compiler change?
It can with enough meta-programming information, as the one the reflection proposal has, yes.

Or maybe you are talking about modifying the language (so still a compiler change) but so is_enum can implement this feature?
Are you talking of static reflection here?



But if the enum has an implied underlying type, then why would you need to know if a particular integer (which does not match any enumerator) is within the valid range for that enum?
Because this is legal. I can assign it a value on the specified range. That's all. If I want my code to be outside the UB world I should be able to check on the conditions. I can of course do it for each particular case, but what we are talking of here is about what the compiler could do for us in a generic way.

The compiler is generating the code so where does generic come into this?
The words generic way were in opposition to each particular case.


What are you trying to do that you need to do this?
This is not a use case I would write myself, but I've see it a lot of times. When you use enums as flags of an bitset
enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

The valid enumerators  don't correspond to the valid range, that in this case is any value between 0 and 8.

If you don't have a problem to be solved with such a function, then there's really no point in adding one.
I was sure you will ask and say this ;-)

I don't know why the new C++11 enum with an explicit underlying type have a different range of valid values.
I'll be interested in knowing the rationale.

Because enums are integers. That's the rationale.
I believe that you didn't understood my question. Let me see with an example.
What is the difference between

    enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

and

    enum class Y : unsigned char { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};
?

X has a valid range 0..N, while Y has a valid range 0..255.

Why do we need this difference? Why forcing the underlying type changes the range of valid values?

I had std::any_enum_value in my earlier post and make_bad_enum knew the type of the enum.
The idea was that those two pieces of information allowed a value to be constructed that could express any enum as intended.
I'm raising that in case it's helpful to this conversation here but it might not be because I can't quite follow what problem is being solved here?
It seems that I should not explain myself correctly as people is not understanding the complementary problem I'm raising.

The question is : Does your any_enum_value check for the enumerators values or the valid range of the enumeration?

Vicente

gmis...@gmail.com

unread,
Jan 12, 2017, 6:35:31 AM1/12/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com

Hi Vincente


On Thursday, January 12, 2017 at 8:45:08 PM UTC+13, Vicente J. Botet Escriba wrote:
Le 11/01/2017 à 21:49, gmis...@gmail.com a écrit :

What is important is the range of values.

The question I have to ask is this: if you have an integer, why do you need to know if it will fit within the range of an enum with an implied underlying type?
Because initializing the enum with an integer out of range is UB.

In my earlier post where I described at length how I thought the API should be, was there UB in that and if so where?
I'm trying to understand where your UB comments are directed as I'm not seeing where this happens.
The interface you have proposed doesn't introduce any UB, it avoid it, because the test is over a subset of the valid values for an enumeration. The UB is defined in standard. See the paragraph I mentioned. What I'm saying is that we need a function that checks also exactly for the valid values. Is that so complex to understand.

What made it complex to me was that you said UB and I didn't see any UB.
Yes I agree we need a function to test the enum values. I do not know why you feel it's  'subset' of enum values though.
sorry 

The question is : Does your any_enum_value check for the enumerators values or the valid range of the enumeration?

My std::any_enum_value does not range check or anything, I envisaged it as a container that promises to be large enough to hold any enum value possible for an ABI, nothing else. As best as I can tell from looking at my earlier post, any_enum_value was left in by mistake from a more complex design where I was thinking where the interface would be more like std::optional.

But I didn't recommend that design in the end so I don't think it was used because bad_enum isn't used either, I used std::range_error and satisfied myself that was ok because the error message would be detailed enough to show what type of enum failed and the value that failed to convert. So a dedicated bad_enum wasn't needed unless we needed to query information out of that. Which I decided we probably didn't.

Hope that helps.

Nicol Bolas

unread,
Jan 12, 2017, 11:07:22 AM1/12/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com


On Thursday, January 12, 2017 at 2:28:00 AM UTC-5, Vicente J. Botet Escriba wrote:
Le 11/01/2017 à 22:21, Nicol Bolas a écrit :
On Wednesday, January 11, 2017 at 1:52:11 PM UTC-5, Vicente J. Botet Escriba wrote:
Le 11/01/2017 à 00:06, Nicol Bolas a écrit :
On Tuesday, January 10, 2017 at 5:26:11 PM UTC-5, Vicente J. Botet Escriba wrote:

The question I have to ask is this: if you have an integer, why do you need to know if it will fit within the range of an enum with an implied underlying type?
Because initializing the enum with an integer out of range is UB.

You're misunderstanding my question.

The situation you describe is one where:

1. You have an integer of arbitrary origin.

2. You want to convert it to an enum type.

3. That integer does not match one of the enumerators in that type.

4. The enum type does not have a fixed underlying type.

You have described very well the context.
What goal are you trying to achieve with all this? Or more to the point, why are you incapable of simply giving the enum an underlying type and thus making the question moot?
Because the enum is declared in a 3pp library in C++98? or a common part that is shared by an application using C++98 and another using C++2x?

So the enum is in legacy code (one way or another). And the enum is not being used as a true enumeration, but as a general value that may or may not resolve to one of the enumerators. And you're converting an arbitrary integer into that enumeration.

Ultimately, I'm not sure that this confluence of issues comes up often enough that we need a mechanism to check for it.
This is not a use case I would write myself, but I've see it a lot of times. When you use enums as flags of an bitset
enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

The valid enumerators  don't correspond to the valid range, that in this case is any value between 0 and 8.

But we can already answer that question. `X` is an `enum class`. As such, it always has a fixed underlying type. If you don't specify one, then it shall be `int`, and therefore `X` can legally assume any `int` value.
From the text of the standard I send before, the range is not the range of int. Could you point me from where are you concluding this?

[dcl.enum]/5:

>  For a scoped enumeration type, the underlying type is int if it is not explicitly specified. In both of these cases, the underlying type is said to be fixed.

[dcl.enum]/8:


> For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type.

So the range of a scoped enumeration is always the range of its underlying type. Therefore the situation you're talking about can only come about by using non-scoped, non-fixed enums.
I believe that you didn't understood my question. Let me see with an example.
What is the difference between

    enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

and

    enum class Y : unsigned char { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};
?

X has a valid range 0..N, while Y has a valid range 0..255.

Why do we need this difference? Why forcing the underlying type changes the range of valid values?

In that case, both have a forced underlying type, as I pointed out above. So the reason for the differing ranges of values is obvious.

Now, let's assume you have revised your example to not use `enum class`. `enum X` does not have a fixed underlying type, so its range is determined by its enumerators. The reason that is different is because that's how it always was before, and there's terribly little reason to change it.
I'm not questioning the old C++98 behavior but the new C++11 behavior (if we can say new for C++11)

The C++11 behavior is much simpler: the range of an enum with a fixed underlying type is the range of its fixed underlying type. C++11 did not change the rules for the enum range of an enum without a fixed underlying type.

Vicente J. Botet Escriba

unread,
Jan 12, 2017, 7:09:31 PM1/12/17
to std-pr...@isocpp.org, m.ce...@gmail.com, gmis...@gmail.com
Thanks this references clarifies the range of valid values. SO the difference isn't between explicit or not , but between enum and scoped enum.

I believe that you didn't understood my question. Let me see with an example.
What is the difference between

    enum class X { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};

and

    enum class Y : unsigned char { NONE=0, A=0x01, B=0x02, C=0x04, ALL 0x08};
?

X has a valid range 0..N, while Y has a valid range 0..255.

Why do we need this difference? Why forcing the underlying type changes the range of valid values?

In that case, both have a forced underlying type, as I pointed out above. So the reason for the differing ranges of values is obvious.

Now, let's assume you have revised your example to not use `enum class`. `enum X` does not have a fixed underlying type, so its range is determined by its enumerators. The reason that is different is because that's how it always was before, and there's terribly little reason to change it.
I'm not questioning the old C++98 behavior but the new C++11 behavior (if we can say new for C++11)

The C++11 behavior is much simpler: the range of an enum with a fixed underlying type is the range of its fixed underlying type. C++11 did not change the rules for the enum range of an enum without a fixed underlying type.
The C++11 behavior contains the C++98 behavior, so it can not be simpler and in my opinion is weird to have a different behavior :)

Thanks anyway for the clarifications.
Vicente

Vicente J. Botet Escriba

unread,
Jan 13, 2017, 2:54:18 AM1/13/17
to std-pr...@isocpp.org, m.ce...@gmail.com, gmis...@gmail.com
I was wondering if the rationale for the different behavior has something to be with the fact that scoped enums can be forward declared.

enum class X;

X f(int i) {
    return X(i); // we are able to construct them independently of the
}

int main()
{  
   X x = f(1);
   return static_cast<int>(x);
}

http://melpon.org/wandbox/permlink/LiGCmCNwENTskhtv

Vicente

Nicol Bolas

unread,
Jan 13, 2017, 10:07:06 AM1/13/17
to ISO C++ Standard - Future Proposals, m.ce...@gmail.com, gmis...@gmail.com

Any enum with a fixed underlying type can be forward declared. As such, I imagine that the range rules are not unrelated to that.
Reply all
Reply to author
Forward
0 new messages