Thread overview
Enum conversion
Apr 21, 2020
Russel Winder
Apr 21, 2020
Russel Winder
Apr 22, 2020
Russel Winder
Apr 21, 2020
tsbockman
Apr 21, 2020
Russel Winder
April 21, 2020
Hi,

Given an enum:

enum ZoneNumber {
    One = 1,
    Two = 2,
}

then which of these is the right way of accessing the value?

cast(ubyte)ZoneNumber.One
to!ubyte(ZoneNumber.One)

conversely what is the right way of going the other way:

cast(ZoneNumber)1
to!ZoneNumber(1)

I tried:

enum ZoneNumber : ubyte {
    One = 1,
    Two = 2,
}

but the members One and Two still seem to be types as int. :-(


-- 
Russel.
===========================================
Dr Russel Winder      t: +44 20 7585 2200
41 Buckmaster Road    m: +44 7770 465 077
London SW11 1EN, UK   w: www.russel.org.uk



April 21, 2020
On 4/21/20 12:03 PM, Russel Winder wrote:
> Hi,
> 
> Given an enum:
> 
> enum ZoneNumber {
>      One = 1,
>      Two = 2,
> }
> 
> then which of these is the right way of accessing the value?
> 
> cast(ubyte)ZoneNumber.One
> to!ubyte(ZoneNumber.One)

I generally do this:

ubyte(ZoneNumber.One)

> 
> conversely what is the right way of going the other way:
> 
> cast(ZoneNumber)1

This will incur zero runtime cost, so I would recommend that.

> to!ZoneNumber(1)

This works too, I think it just does the equivalent of the first, but if not inlined, you will incur some runtime cost.

> 
> I tried:
> 
> enum ZoneNumber : ubyte {
>      One = 1,
>      Two = 2,
> }
> 
> but the members One and Two still seem to be types as int. :-(
They are typed as ZoneNumber, which is a derivative of ubyte. What measurement are you doing to determine that they are int?

auto x = ZoneNumber.One;
ubyte y = x; // fine

If you leave off the :ubyte part, the declaration of y would fail.

Similarly, this would fail:

enum ZoneMember : ubyte {
   One = 1,
   Two = 2,
   ThreeThousand = 3000, //  Error: cannot implicitly convert expression 3000 of type int to ubyte
}

-Steve
April 21, 2020
On Tuesday, 21 April 2020 at 16:03:20 UTC, Russel Winder wrote:
> then which of these is the right way of accessing the value?
>
> cast(ubyte)ZoneNumber.One
> to!ubyte(ZoneNumber.One)

Either is acceptable because there is no way that this operation can fail. Using a naked `cast` makes less work for the compiler and performs optimally with or without inlining, though.

> conversely what is the right way of going the other way:
>
> cast(ZoneNumber)1
> to!ZoneNumber(1)

Use `to` except where you can gaurantee that the input value maps to a valid enum member, because `cast` does not check your work:

    writeln(cast(ZoneNumber)17); // breaks the type system
    writeln(to!ZoneNumber(17)); // throws a ConvException: Value (17) does not match any member value of enum 'ZoneNumber'

So, `cast` is faster, but unsafe. `to` is slower, but protects the enum's invariant.
April 21, 2020
On Tue, 2020-04-21 at 12:59 -0400, Steven Schveighoffer via Digitalmars-d-learn wrote:
> On 4/21/20 12:03 PM, Russel Winder wrote:
> > Hi,
> > 
> > Given an enum:
> > 
> > enum ZoneNumber {
> >      One = 1,
> >      Two = 2,
> > }
> > 
> > then which of these is the right way of accessing the value?
> > 
> > cast(ubyte)ZoneNumber.One
> > to!ubyte(ZoneNumber.One)
> 
> I generally do this:
> 
> ubyte(ZoneNumber.One)

Hummm… I hadn't got to that one. :-)

Why choose that rather than one of the other two?


> 
> > conversely what is the right way of going the other way:
> > 
> > cast(ZoneNumber)1
> 
> This will incur zero runtime cost, so I would recommend that.
> 
> > to!ZoneNumber(1)
> 
> This works too, I think it just does the equivalent of the first, but
> if
> not inlined, you will incur some runtime cost.
> 
> > I tried:
> > 
> > enum ZoneNumber : ubyte {
> >      One = 1,
> >      Two = 2,
> > }
> > 
> > but the members One and Two still seem to be types as int. :-(
> They are typed as ZoneNumber, which is a derivative of ubyte. What measurement are you doing to determine that they are int?

typeof(ZoneNumber.One).stringof seemed to return uint.

> 
> auto x = ZoneNumber.One;
> ubyte y = x; // fine
> 
> If you leave off the :ubyte part, the declaration of y would fail.
> 
> Similarly, this would fail:
> 
> enum ZoneMember : ubyte {
>     One = 1,
>     Two = 2,
>     ThreeThousand = 3000, //  Error: cannot implicitly convert
> expression 3000 of type int to ubyte
> }

Oooo… I like this, adding ":ubyte" immediately.

-- 
Russel.
===========================================
Dr Russel Winder      t: +44 20 7585 2200
41 Buckmaster Road    m: +44 7770 465 077
London SW11 1EN, UK   w: www.russel.org.uk



April 21, 2020
On Tue, 2020-04-21 at 18:09 +0000, tsbockman via Digitalmars-d-learn wrote:
> On Tuesday, 21 April 2020 at 16:03:20 UTC, Russel Winder wrote:
> > then which of these is the right way of accessing the value?
> > 
> > cast(ubyte)ZoneNumber.One
> > to!ubyte(ZoneNumber.One)
> 
> Either is acceptable because there is no way that this operation can fail. Using a naked `cast` makes less work for the compiler and performs optimally with or without inlining, though.

Seems like in this case cast is better than to! use.

> > conversely what is the right way of going the other way:
> > 
> > cast(ZoneNumber)1
> > to!ZoneNumber(1)
> 
> Use `to` except where you can gaurantee that the input value maps to a valid enum member, because `cast` does not check your work:
> 
>      writeln(cast(ZoneNumber)17); // breaks the type system
>      writeln(to!ZoneNumber(17)); // throws a ConvException: Value
> (17) does not match any member value of enum 'ZoneNumber'
> 
> So, `cast` is faster, but unsafe. `to` is slower, but protects the enum's invariant.

Thanks. It seems to! is de rigueur in this case.

-- 
Russel.
===========================================
Dr Russel Winder      t: +44 20 7585 2200
41 Buckmaster Road    m: +44 7770 465 077
London SW11 1EN, UK   w: www.russel.org.uk



April 21, 2020
On 4/21/20 3:00 PM, Russel Winder wrote:
> On Tue, 2020-04-21 at 12:59 -0400, Steven Schveighoffer via
> Digitalmars-d-learn wrote:
>> On 4/21/20 12:03 PM, Russel Winder wrote:
>>> Hi,
>>>
>>> Given an enum:
>>>
>>> enum ZoneNumber {
>>>       One = 1,
>>>       Two = 2,
>>> }
>>>
>>> then which of these is the right way of accessing the value?
>>>
>>> cast(ubyte)ZoneNumber.One
>>> to!ubyte(ZoneNumber.One)
>>
>> I generally do this:
>>
>> ubyte(ZoneNumber.One)
> 
> Hummm… I hadn't got to that one. :-)
> 
> Why choose that rather than one of the other two?

1. it's shorter and prettier.
2. No cast (I avoid using cast whenever I can).
3. No gotcha type conversions.

e.g. for point 3:

enum ZoneMember { // : int
  One = 1,
  Two = 2,
  ReallyBig = 4567,
}

auto b1 = ubyte(ZoneNumber.One); // 1 (compiler uses VRP to make this work)
auto b2 = ubyte(ZoneNumber.ReallyBig); // Compiler error

vs.

auto b1 = cast(ubyte)ZoneNumber.One; // 1
auto b2 = cast(ubyte)ZoneNumber.ReallyBig; // b2 == 215 (truncated)

vs.

auto b1 = to!ubyte(ZoneNumber.One); // 1
auto b2 = to!ubyte(ZoneNumber.ReallyBig); // runtime error

> 
> 
>>
>>> conversely what is the right way of going the other way:
>>>
>>> cast(ZoneNumber)1
>>
>> This will incur zero runtime cost, so I would recommend that.
>>
>>> to!ZoneNumber(1)
>>
>> This works too, I think it just does the equivalent of the first, but
>> if
>> not inlined, you will incur some runtime cost.
>>
>>> I tried:
>>>
>>> enum ZoneNumber : ubyte {
>>>       One = 1,
>>>       Two = 2,
>>> }
>>>
>>> but the members One and Two still seem to be types as int. :-(
>> They are typed as ZoneNumber, which is a derivative of ubyte. What
>> measurement are you doing to determine that they are int?
> 
> typeof(ZoneNumber.One).stringof seemed to return uint.

This is what happens for me:

enum ZoneNumber : ubyte {
  One = 1,
  Two = 2,
}

pragma(msg, typeof(ZoneNumber.One).stringof); // ZoneNumber

-Steve
April 21, 2020
On 4/21/20 2:09 PM, tsbockman wrote:
>> conversely what is the right way of going the other way:
>>
>> cast(ZoneNumber)1
>> to!ZoneNumber(1)
> 
> Use `to` except where you can gaurantee that the input value maps to a valid enum member, because `cast` does not check your work:
> 
>      writeln(cast(ZoneNumber)17); // breaks the type system
>      writeln(to!ZoneNumber(17)); // throws a ConvException: Value (17) does not match any member value of enum 'ZoneNumber'
> 
> So, `cast` is faster, but unsafe. `to` is slower, but protects the enum's invariant.

I just want to correct this and say there isn't a type system requirement for the enum to be only one of the selected values, even in safe code.

e.g.:

enum flags {
  one = 1,
  two = 2,
}

flags f = flags.one | flags.two; // ok
++f; // ok also

In essence, an enum acts as a derived type with named constants.

Also, there is one situation where you can't use to -- a string-based enum:

enum sym : string {
   s = "s value",
   y = "y value"
}

auto a = cast(sym)"s value"; // ok
assert(a == sym.s);

auto b = to!sym("s value"); // runtime error

This is because to!someEnum(string) is specialized to look at the enum names only, not the values.

-Steve
April 22, 2020
On Tue, 2020-04-21 at 15:48 -0400, Steven Schveighoffer via
Digitalmars-d-learn wrote:
[…]
> 
> 1. it's shorter and prettier.
> 2. No cast (I avoid using cast whenever I can).
> 3. No gotcha type conversions.

Works for me, you have me convinced. :-)

> e.g. for point 3:
> 
> enum ZoneMember { // : int
>    One = 1,
>    Two = 2,
>    ReallyBig = 4567,
> }
> 
> auto b1 = ubyte(ZoneNumber.One); // 1 (compiler uses VRP to make this
> work)
> auto b2 = ubyte(ZoneNumber.ReallyBig); // Compiler error
> 
> vs.
> 
> auto b1 = cast(ubyte)ZoneNumber.One; // 1
> auto b2 = cast(ubyte)ZoneNumber.ReallyBig; // b2 == 215 (truncated)
> 
> vs.
> 
> auto b1 = to!ubyte(ZoneNumber.One); // 1
> auto b2 = to!ubyte(ZoneNumber.ReallyBig); // runtime error

QED.

Though for converting a ulong to a ubyte, I am assuming to!ubyte(x) is
the right tool for the job.

[…]
> pragma(msg, typeof(ZoneNumber.One).stringof); // ZoneNumber

Which is as it should be really. I was clearly having a mental
aberration. Anyway, the probem is now solved! :-) Thanks for your
input, much appreciated.

-- 
Russel.
===========================================
Dr Russel Winder      t: +44 20 7585 2200
41 Buckmaster Road    m: +44 7770 465 077
London SW11 1EN, UK   w: www.russel.org.uk



April 22, 2020
On 4/22/20 6:36 AM, Russel Winder wrote:
> Though for converting a ulong to a ubyte, I am assuming to!ubyte(x) is
> the right tool for the job.

It depends! If you know that the long will fit in a ubyte, by all means just use a cast. It's the fastest option. If you have no idea the value of the long, but it's *supposed* to fit into a ubyte, use to if you want an exception for those outside the range. And if you don't care, and just want a ubyte, use a cast.

But the compiler isn't going to accept ubyte(someLong).

-Steve