Jump to page: 1 2
Thread overview
Setting up a final switch from a list
Aug 07
Dom DiSc
enum ops - Re: Setting up a final switch from a list
Aug 09
kdevel
August 07

On Wednesday, 2 August 2023 at 18:19:55 UTC, Cecil Ward wrote:

>

Am I right in thinking that final switch can be used to check that all the elements in an enum are handled in cases?

final switch is not considered safe with enums because it can take interpolated values, resulting in a runtime error:

import std.stdio : writeln;

enum alphabet { a, b, c }

string lookUp(alphabet value)
{
    with(alphabet) final switch(value)
    {
        case a: return "letter a"; break;
        case b: return "letter b"; break;
        case c: return "letter c"; break;
    }
}

void main()
{
    lookUp(cast(alphabet)0).writeln; // okay
    
    alphabet.b.lookUp.writeln; // okay

    auto abc = alphabet.c;
    abc.lookUp.writeln; // okay
    
    auto abd = cast(alphabet)3;
    //abd.lookUp.writeln; // runtime-error:
    /* core.exception.SwitchError
    @onlineapp.d(7): No appropriate switch clause found//*/
}

SDB@79

August 07

On Monday, 7 August 2023 at 17:08:56 UTC, Salih Dincer wrote:

>

On Wednesday, 2 August 2023 at 18:19:55 UTC, Cecil Ward wrote:

>

Am I right in thinking that final switch can be used to check that all the elements in an enum are handled in cases?

final switch is not considered safe with enums because it can take interpolated values, resulting in a runtime error:

enum alphabet { a, b, c }
auto abd = cast(alphabet)3;

If anything, then this cast is not @safe.
Also arithmetic operations on enums shall never result in enums, but instead will be implicitly cast to integers before the operation. And implicit cast from int to enums don't exist.

So in @safe code you have to do this cast explicitly and trust it explicitly - and you should not be surprised if trusted code does something bad. That's why you should be extra careful before you trust a cast (or other unsafe operation).

August 08

On Monday, 7 August 2023 at 17:25:05 UTC, Dom DiSc wrote:

>

If anything, then this cast is not @safe.

I think that casting int to enum is actually memory safe, though it is not type safe.

>

Also arithmetic operations on enums shall never result in enums, but instead will be implicitly cast to integers before the operation.

Unfortunately this is not the case for all operations on enums:

auto e = alphabet.a;
e |= alphabet.c; // allowed

I think bit shifts are allowed too. Due to these, final swift has to always generate a default case.

Walter's sum types proposal would provide a type safe enum.

August 08

On 8/8/23 4:28 AM, Nick Treleaven wrote:

>

On Monday, 7 August 2023 at 17:25:05 UTC, Dom DiSc wrote:

>

If anything, then this cast is not @safe.

I think that casting int to enum is actually memory safe, though it is not type safe.

Yes, casting an enum is @safe.

> >

Also arithmetic operations on enums shall never result in enums, but instead will be implicitly cast to integers before the operation.

Unfortunately this is not the case for all operations on enums:

auto e = alphabet.a;
e |= alphabet.c; // allowed

I think bit shifts are allowed too. Due to these, final swift has to always generate a default case.

If you read the integer promotion spec, if both operands are the same enum, the result is the same enum.

https://dlang.org/spec/type.html#usual-arithmetic-conversions

(scroll down to the enum part)

-Steve

August 08

On Tuesday, 8 August 2023 at 14:06:50 UTC, Steven Schveighoffer wrote:

>

If you read the integer promotion spec, if both operands are the same enum, the result is the same enum.

https://dlang.org/spec/type.html#usual-arithmetic-conversions

(scroll down to the enum part)

OK, thanks.

enum E { a, b, c }

void f()
{
    E e = E.a;
    e |= E.c; // same enum, so allowed per spec
    e = e >> 3; // RHS should be int, which shouldn't convert to E
}

The spec says:

>

If one operand is an enum and the other is the base type of that enum, the result is the base type.

So e >> 3 should be int and the last line of f should not compile. But it does. Is the spec wrong or the compiler?

August 08

On 8/8/23 12:34 PM, Nick Treleaven wrote:

>
enum E { a, b, c }

void f()
{
     E e = E.a;
     e |= E.c; // same enum, so allowed per spec
     e = e >> 3; // RHS should be int, which shouldn't convert to E
}

The spec says:

>

If one operand is an enum and the other is the base type of that enum, the result is the base type.

So e >> 3 should be int and the last line of f should not compile. But it does. Is the spec wrong or the compiler?

It appears that bit shifting doesn't count:

auto x = uint.max << 1L;
static assert(is(typeof(x)) == uint));

https://dlang.org/spec/expression.html#shift_expressions

>

The operands must be integral types, and undergo the Integer Promotions. The result type is the type of the left operand after the promotions.

That seems incorrect given the test above.

This is a quite old part of the compiler, and a lot of the spec is written to describe the behavior of the compiler.

-Steve

August 08

On Tuesday, 8 August 2023 at 18:29:42 UTC, Steven Schveighoffer wrote:

> > >

If one operand is an enum and the other is the base type of that enum, the result is the base type.

So e >> 3 should be int and the last line of f should not compile. But it does. Is the spec wrong or the compiler?

It appears that bit shifting doesn't count:

auto x = uint.max << 1L;
static assert(is(typeof(x)) == uint));

https://dlang.org/spec/expression.html#shift_expressions

Ah, I see.

> >

The operands must be integral types, and undergo the Integer
Promotions. The result type is the type of the left operand after the promotions.

That seems incorrect given the test above.

Yes, looks like the result is the left operand type without promotions.

August 09

On Monday, 7 August 2023 at 17:08:56 UTC, Salih Dincer wrote:

>

final switch is not considered safe with enums because it can take interpolated values, resulting in a runtime error:

Wasn't Cecil concerned with how to ensure at compile time that for all values of an enum there is a handler? I'll post my unsent draft from before the restore here:

On Wednesday, 2 August 2023 at 18:19:55 UTC, Cecil Ward wrote:

>

Am I right in thinking that final switch can be used to check that all the elements in an enum are handled in cases?

According to [1] this is the case if and only if the switch expression is of that enum type.

// compiles with dmd -version=X
// missing case qmark if compiled without version
import std.stdio;
import std.conv;
import std.exception;

enum terminators_t { percent = '%', colon = ':', qmark = '?' };

int main_ (string [] args)
{
   enforce (args.length == 2, "need one arg");
   enforce (args [1].length == 1, "arg must be one character");
   auto input = args [1][0].to!terminators_t;
   final switch (input) {
      case terminators_t.percent:
         "PERCENT".writeln;
         break;
      case terminators_t.colon:
         "COLON".writeln;
         break;
version (X) {
      case terminators_t.qmark:
         "QUESTION MARK".writeln;
         break;
}
   }
   return 0;
}

int main (string [] args)
{
   try return main_ (args);
   catch (Exception e) {
      stderr.writeln (e.msg);
      return 1;
   }
}

But I like it without indentation, switch and break:

import std.stdio;
import std.exception;

void percent () { "PERCENT".writeln; }
void colon () { "COLON".writeln; }
void qmark () { "QUESTION MARK".writeln; }

enum terminatorhandlers = [ // this is implicitly "final"
   '%' : &percent,
   ':' : &colon,
   '?' : &qmark,
];

int main_ (string [] args)
{
   enforce (args.length == 2, "need one arg");
   enforce (args [1].length == 1, "arg must be one character");
   auto input = args [1] [0];
   auto p = input in terminatorhandlers; // switch emulation
   enforce (p, "unknown terminator " ~ input);
   (*p) (); // call the registered action
   return 0;
}

int main (string [] args)
{
   try return main_ (args);
   catch (Exception e) {
      stderr.writeln (e.msg);
      return 1;
   }
}

[1] https://dlang.org/spec/statement.html#final-switch-statement

August 10
I think one part of the original question (now fanished in the nntp backup) was how to get all enum members into a list without duplicating code.


```d
import std.traits : EnumMembers;
import std.stdio : writeln;
import std.algorithm : map;
import std.conv : to;
enum alphabet {
  a, b, c, d
}

void main()
{
  writeln(EnumMembers!alphabet);
  writeln([EnumMembers!alphabet]);
  writeln([EnumMembers!alphabet].map!(a => "test" ~a.to!string));
}
```

results in

```
abcd
[a, b, c, d]
["testa", "testb", "testc", "testd"]```
```


March 28

On Thursday, 10 August 2023 at 08:33:13 UTC, Christian Köstlin wrote:

>

I think one part of the original question (now fanished in the nntp backup) was how to get all enum members into a list without duplicating code.

import std.traits : EnumMembers;
import std.stdio : writeln;
import std.algorithm : map;
import std.conv : to;
enum alphabet {
  a, b, c, d
}

void main()
{
  writeln(EnumMembers!alphabet);
  writeln([EnumMembers!alphabet]);
  writeln([EnumMembers!alphabet].map!(a => "test" ~a.to!string));
}

results in

abcd
[a, b, c, d]
["testa", "testb", "testc", "testd"]```

How can we add all members of an enum type to a list without duplicating code?

I wonder if this is something we'll see soon as the D language feature?

In the D programming language, this can be achieved using features provided by the language such as __traits and AliasSeq. For instance, the EnumMembers trait from the std.traits module returns all members of an enum type as a tuple. This tuple contains the enum members in sequence, allowing for iteration over them or conversion into a list.

Finally, utilizing these language features to avoid code duplication and write cleaner, more maintainable code is a good practice. Sorry to resurrect this old thread, but what do you think; people living in 2024 ?

SDB@79

« First   ‹ Prev
1 2