www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - enums and version/static if/"inheritance"

reply Witold Baryluk <witold.baryluk+d gmail.com> writes:
I do often develop some low level codes, often interacting with 
Linux kernel, or some embedded systems. Where unfortunately some 
definitions of various constants and interfaces vary vastly 
between architectures or oses, or compilation options.

My today complain would be with `enums`.

A good context is just checking various `mman.d` files in phobos, 
where a number of top level `enum` values are defined using mix 
of version and static ifs, or even a bit of logic for fallback.

Unfortunately this only works for top level enum values. It does 
not work for named enum types.

A simple example

```d
enum FOO {
   A = 5,
   B = 6,
version (x86_64) {
   C = 7,
} else version (AArch64) {
   C = 17,
} else {
   static assert(0);
}
   version E = 9,
}

// static assert(checkUniqueValues!FOO);
```

Is simply not supported (not even that surprising considering 
that enum members are separated by comma, not a semicolon).


Same with `static if`.

Doing `enum FooBase { ... }  version () ... enum FOO : FooBase { 
... }` will not work (it does something else that expected)

One will say, how about this:

```d
enum FOO {
   A = 5,
   B = 6,
   C = ((){ version (x86_64) return 7; else return 17; })(),
   E = 9,
}


// static assert(checkUniqueValues!FOO);
```

That is unfortunately not good enough, and very verbose. There 
are often multiple values that need to have these branching 
structure, and now one is forced to repeat it multiple time.

And even worse, this only allows defining enum members that only 
differ in value. One cannot use this method to decide if enum 
member/value exists or not (or maybe put  deprecated or other UDA 
on them conditionally).

Example might be on linux x86_64 one should only use 
`HUGE_TLB_2MB` and `HUGE_TLB_1GB`, but on ppc64, different values 
are allowed, like only `HUGE_TLB_16MB` is allowed. In linux uapi 
/ libc all values are defined on all archs, but using wrong ones 
will definitively will cause a runtime error, and it would be 
preferably that library just hides unsupported ones by default.

Another example might be enum with some values hidden because 
they are broken / deprecated, but some special `version` could be 
used to enabled if one knows what they are doing (Example on 
linux might be some syscall numbers, which are deprecated or 
essentially broken, and superseded by better interfaces, example 
might be `tkill`, or `renameat`).


And last consideration, again for `mmap` flags.

There is a set of mmap flags defined by posix (the actual values 
might different between systems  and even archs). And a bunch of 
extra flags supported on various systems. In Phobos these is done 
by just having top level enum values (not part of any enum type), 
and importing proper modules with these values.

This makes impossible to write "type-safe" wrappers around `mmap` 
(lets assume we are calling Linux kernel directly so we can 
ignore all glibc / musl stuff, and we can invent a new 
interface), and flags must be just int.

In ideal world, one would `import ...linux.mman : mmap`, and it 
would have signature:

```d
module ...linux.mman;

version (X86_64) {
void* mmap(void* p, size_t, MMapProt prot, MMapFlags flags, int 
fd, off_t off) {
   return cast(void*)syscall!(Syscall.MMAP)(cast(ulong)p, 
cast(uint)prot, ...)
}
}
```

where for example MMapFlags would be (hypothetically):

```d
module ...linux.mman;

enum MMapFlags : imported!"...posix.mman").MMapFlags {
   // some extra linux specific flags
   MAP_DROPPABLE = 0x08,
   MAP_UNINITIALIZED = 0x4000000,
}
```

and common ones:

```d
module ...posix.mman;

enum MMapFlags {
   version (linux) {
     MAP_PRIVATE = 0x02,
   }
   // ...
}
```


So only options are:

1. Redefine entire struct of the enum (including comments, and 
expressions, i.e. if some enum member values are expression, like 
`MAP_HUGE_1GB = 30U << HUGETLB_FLAG_ENCODE_SHIFT,`) for every 
possibly combination of system, architecture, and in some cases 
even libc. This does not scale well.

2. Create some kind of string DSL, CTFEs and `mixin`s to build 
the full enum definition, something like this possible.

```d
mixin(buildEnum(`
   A 5
   B 6
   C 7 if x86_64
   C 17 if AArch64
   E 9
`));
```

And then provide some reflection based enum inheritance (where 
enum member values or their presence can be also be steered by 
some static ifs / version logic). Surely doable, but not very 
clean.

But it is pretty ugly and limiting, not to mention probably does 
not play well with most IDEs.


Maybe something like this would be interesting too.

```d
enum FOO {
   A = 5,
   B = 6,
   mixin("C = 7")
} else version (AArch64) {
   C = 17,
} else {
   static assert(0);
}
   version E = 9,
}

// static assert(checkUniqueValues!FOO);
```

3. Use typedef, to create new distinct type for these various 
flag constants, and use that.

Maybe

```d

import std.typecons;

alias Foo = Typedef!int;

enum Foo B = 6;

```


But then the actual structure and discoverability is lost. Plus 
if one has two classes of values both having `B`, they will 
clash, unless one puts some prefixes, or puts them in different 
modules, which is a big limitation.

I am not advocating for extending language necessarily, as I am 
sure library solution could be worked out too. Still it is a bit 
strange that one cannot easily use `version` and `static if` in 
`enum`s, where they can use it in almost every other place in the 
language.
Jul 28
next sibling parent Denis Feklushkin <feklushkin.denis gmail.com> writes:
On Sunday, 28 July 2024 at 22:20:47 UTC, Witold Baryluk wrote:

This is the same thing?

https://issues.dlang.org/show_bug.cgi?id=24666
Jul 29
prev sibling next sibling parent reply Nick Treleaven <nick geany.org> writes:
On Sunday, 28 July 2024 at 22:20:47 UTC, Witold Baryluk wrote:
 Unfortunately this only works for top level enum values. It 
 does not work for named enum types.

 A simple example

 ```d
 enum FOO {
   A = 5,
   B = 6,
 version (x86_64) {
   C = 7,
 } else version (AArch64) {
   C = 17,
 } else {
   static assert(0);
 }
   version E = 9,
 }
 ```
Possible workaround if each enum member is given a specific initializer: ```d struct FooEnum { int A = 5, B = 6; version (x86_64) { int C = 7; } else version (AArch64) { int C = 17; } else { static assert(0); } } mixin enumGen!(FooEnum, "FOO"); static assert(FOO.A == 5); static assert(FOO.B == 6); version (x86_64) static assert(FOO.C == 7); template enumGen(T, string name) { private string _gen() { T v; auto r = "enum " ~ name ~ " {"; foreach (m; __traits(allMembers, T)) { import std.conv; r ~= m ~ "=" ~ __traits(getMember, v, m).to!string ~ ","; } r ~= "}"; return r; } mixin(_gen); } ```
Jul 29
parent reply Nick Treleaven <nick geany.org> writes:
On Monday, 29 July 2024 at 11:25:45 UTC, Nick Treleaven wrote:
 Possible workaround if each enum member is given a specific 
 initializer:
 ```d
 struct FooEnum
 {
     int A = 5, B = 6;
     version (x86_64) {
         int C = 7;
     } else version (AArch64) {
         int C = 17;
     } else {
         static assert(0);
     }
 }

 mixin enumGen!(FooEnum, "FOO");
Of course, the mixin there is not doing anything more than a struct with enum members: ```d version = x86_64; struct FOO { enum A = 5, B = 6; version (x86_64) { enum C = 7; } else version (AArch64) { enum C = 17; } else { static assert(0); } } static assert(FOO.A == 5); static assert(FOO.B == 6); version (x86_64) static assert(FOO.C == 7); ``` But potentially the mixin approach with int members could do more - e.g. auto increment any int members without initializer (i.e. whose value is zero), based on the previous lexical member initializer.
Jul 30
parent Witold Baryluk <witold.baryluk+d gmail.com> writes:
On Tuesday, 30 July 2024 at 19:32:45 UTC, Nick Treleaven wrote:
 On Monday, 29 July 2024 at 11:25:45 UTC, Nick Treleaven wrote:
 Possible workaround if each enum member is given a specific 
 initializer:
 ```d
 struct FooEnum
 {
     int A = 5, B = 6;
     version (x86_64) {
         int C = 7;
     } else version (AArch64) {
         int C = 17;
     } else {
         static assert(0);
     }
 }

 mixin enumGen!(FooEnum, "FOO");
Of course, the mixin there is not doing anything more than a struct with enum members: ```d version = x86_64; struct FOO { enum A = 5, B = 6; version (x86_64) { enum C = 7; } else version (AArch64) { enum C = 17; } else { static assert(0); } } static assert(FOO.A == 5); static assert(FOO.B == 6); version (x86_64) static assert(FOO.C == 7); ``` But potentially the mixin approach with int members could do more - e.g. auto increment any int members without initializer (i.e. whose value is zero), based on the previous lexical member initializer.
The struct trick is pretty nice. I will give it a go. At least it should work for the time being. I will try to type-safe it a bit more. I.e. so doing something like `Foo.A | Foo.B` results in some (compile time) constant with proper type and not bare int. So functions receiving these enums can properly detect pass of naked integers, and such. This has a potential.
Jul 30
prev sibling next sibling parent reply IchorDev <zxinsworld gmail.com> writes:
I’ve recently made a API for doing this in BindBC-Common. You can 
see it on the latest commit of my repository 
[here](https://github.com/BindBC/bindbc-common/blob/f9b30591e05238fcda80bb6b070acacd1f77f018/source/bindbc/com
on/codegen.d#L500). This is not tagged as a version yet, but you can download
it & use `dub add-local` until I tag it.

It’s designed for making language bindings (mostly to C) so 
there’s a convenient way to have a D version of the enum (e.g. 
`Plant.flower`) and a C version (e.g. `PLANT_FLOWER`) with the 
same code. And you can turn them on and off easily so that people 
who don’t like one of the styles are still happy.
Usage is like…
```d
import bindbc.common;

mixin(makeEnumBindFns(cStyle: true, dStyle: true)); //this 
defines `makeEnumBind`

//makes `enum Plant: ulong`:
mixin(makeEnumBind(q{Plant}, q{ulong}, members: (){
   EnumMember[] ret = [
     {{q{flower}, q{PLANT_FLOWER}}, q{1}},
     {{q{bush},   q{PLANT_BUSH}},   q{2}},
   ];
   if(condition){
     EnumMember[] add = [
       {{q{herb},   q{PLANT_HERB}},   q{4}},
     ];
     ret ~= add;
   }
   return ret;
}()));
```
Please note that some parameters and fields are **required** to 
be referred to by name. Read the documentation comments for 
`makeEnumBind`, `EnumMember`, and `EnumIden` to avoid misusing 
them.
Jul 30
parent reply Witold Baryluk <witold.baryluk+d gmail.com> writes:
On Tuesday, 30 July 2024 at 23:26:58 UTC, IchorDev wrote:
 I’ve recently made a API for doing this in BindBC-Common. You 
 can see it on the latest commit of my repository 
 [here](https://github.com/BindBC/bindbc-common/blob/f9b30591e05238fcda80bb6b070acacd1f77f018/source/bindbc/com
on/codegen.d#L500). This is not tagged as a version yet, but you can download
it & use `dub add-local` until I tag it.

 It’s designed for making language bindings (mostly to C) so 
 there’s a convenient way to have a D version of the enum (e.g. 
 `Plant.flower`) and a C version (e.g. `PLANT_FLOWER`) with the 
 same code. And you can turn them on and off easily so that 
 people who don’t like one of the styles are still happy.
 Usage is like…
 ```d
 import bindbc.common;

 mixin(makeEnumBindFns(cStyle: true, dStyle: true)); //this 
 defines `makeEnumBind`

 //makes `enum Plant: ulong`:
 mixin(makeEnumBind(q{Plant}, q{ulong}, members: (){
   EnumMember[] ret = [
     {{q{flower}, q{PLANT_FLOWER}}, q{1}},
     {{q{bush},   q{PLANT_BUSH}},   q{2}},
   ];
   if(condition){
     EnumMember[] add = [
       {{q{herb},   q{PLANT_HERB}},   q{4}},
     ];
     ret ~= add;
   }
   return ret;
 }()));
 ```
 Please note that some parameters and fields are **required** to 
 be referred to by name. Read the documentation comments for 
 `makeEnumBind`, `EnumMember`, and `EnumIden` to avoid misusing 
 them.
All nice and dandy. I know how to write mixins, there are many ways to do it. I am just surprised language feature of `version` and `static if` that work so easily in many other places, cannot be used for anything useful in enums and enums only. Is it really like so rare that it is not in the language? Usually one would not want to define an enum that have different values and enum members, as this can mess other code. But then it is pretty normal when interfacing with other languages and systems, or even doing things like changing some default values depending on a version. Phobos (especially core and sys and stdc) with free standing enums, partially due to this problem. I do like idea of the struct with static enum members. It might just work, without CTFEs and mixins. I will give it a try.
Jul 30
parent IchorDev <zxinsworld gmail.com> writes:
On Wednesday, 31 July 2024 at 00:50:59 UTC, Witold Baryluk wrote:
 All nice and dandy. I know how to write mixins, there are many 
 ways to do it.
I am just providing an existing solution that is tailored to language bindings and should see extensive use in the near future and will therefore be less likely to have bugs in the long run.
 I am just surprised language feature of `version` and `static 
 if` that work so easily in many other places, cannot be used 
 for anything useful in enums and enums only.
Judging by the replies to [my DIP ideas thread](https://forum.dlang.org/thread/gkvhaaqrnbizhacpikde forum.dlang.org), I think it’s partly an issue with allowing a declaration in the middle of one type of comma-separated list when it is not permitted in all others: ```d int[] x = [1,2,3, version(X) 4 else 0]; void fn(int a, version(X) int b, int c){} ``` The options are really: 1. We make a way of declaring enums with statements instead of a list, much like in Swift, which would also allow things like enum member functions; or 2. We allow for conditional compilation declarations in all comma separated lists. I don’t see much use of this for function parameters outside of language bindings, but with array literals this could alleviate having to laboriously concatenate many items together, and with associative array literals this would be **very** useful since you can’t just concatenate them easily. I’d love to see if there’s any interest in either of those possibilities.
 Usually one would not want to define an enum that have 
 different values and enum members, as this can mess other code. 
 But then it is pretty normal when interfacing with other 
 languages and systems, or even doing things like changing some 
 default values depending on a version.
Yes, it’s mostly needed for making language bindings, so that means that most people don’t understand why you’d even want the feature; but as someone who creates a lot of language bindings, I absolutely see the utility.
 I do like idea of the struct with static enum members. It might 
 just work, without CTFEs and mixins. I will give it a try.
It’s a clever workaround but won’t work the way people want with a lot of enum-based meta-programming (e.g. `std.conv.to`). You should also ` disable this();`.
Jul 30
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
This comes up from time to time.

```
enum FOO {
    A = 5,
    B = 6,
version (x86_64) {
    C = 7,
} else version (AArch64) {
    C = 17,
} else {
    static assert(0);
}
    version E = 9,
}
```

I've seen this forever in C. It just makes my brain bleed. Here's the D way:

```
version (x86_64)
{
     enum FOO
     {
        A = 5,
        B = 6,
        C = 7,
     }
}
else version (AArch64)
{
    enum FOO
    {
        A = 5,
        B = 6,
        C = 17,
    }
}
else
     static assert(0);
```

Ah, doesn't that look nicer? It's nicer because the code forms a regular 
pattern. A regular pattern is easier to understand. It also uncovers errors, 
like the `version E = 9,` error in the opening example.

Also, when I'm debugging AArch64 code, I don't want to see x86_64 code 
interleaved in with it. Such makes it too easy to inadvertently mix them up.
Jul 30
next sibling parent reply IchorDev <zxinsworld gmail.com> writes:
On Wednesday, 31 July 2024 at 06:11:15 UTC, Walter Bright wrote:
 Here's the D way:

 ```
 version (x86_64)
 {
     enum FOO
     {
        A = 5,
        B = 6,
        C = 7,
     }
 }
 else version (AArch64)
 {
    enum FOO
    {
        A = 5,
        B = 6,
        C = 17,
    }
 }
 else
     static assert(0);
 ```

 Ah, doesn't that look nicer? It's nicer because the code forms 
 a regular pattern. A regular pattern is easier to understand.
Could you please rewrite the D version of [this enum](https://github.com/BindBC/bindbc-glib/blob/b5b8131226f176bcd1ab68af0402c2ce984c2f64/source/ lib/unicode.d#L130) to use this pattern for me? When you’re done, post it as a reply so we can see how much nicer it looks. ;) There’s only 14 different possible versions, and each one is at least 67 lines long (unless you inline the enum, in which case you can kiss readability goodbye), so it should only be a bit over 1000 lines of code when you’re finished there. Compared with the 238 lines of my original version—which could be further shortened if it was using conditional compilation instead of meta-programming—and it might become apparent that your suggestion forsakes the DRY principle.
Jul 31
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/31/2024 2:23 AM, IchorDev wrote:
 your suggestion forsakes the DRY principle.
Indeed, it does. Consider the evolution of expertise: 1. novice - follows the rules because he's told to 2. master - follows the rules because he understands the point of the rules 3. guru - violates the rules because he understands when the rules don't apply BTW, your example looks fine as it is!
Jul 31
parent reply IchorDev <zxinsworld gmail.com> writes:
On Wednesday, 31 July 2024 at 19:03:32 UTC, Walter Bright wrote:
 Indeed, it does. Consider the evolution of expertise:
 1. novice - follows the rules because he's told to
 2. master - follows the rules because he understands the point 
 of the rules
 3. guru - violates the rules because he understands when the 
 rules don't apply
He this, he that. Where is ‘they’ when you need it? Nonetheless, when you say to do things one way, people will feel that they have to follow. I’ve seen a **lot** of horrendous enum repetition in D because of this mentality that ‘we have to do things the bad way because it’s correct’. Before I took over BindBC-SDL it had **so** much repetition because of this design pattern. I replaced all the enum types with typeless lists, but now people can’t just iterate over the members of an enum properly. Relegating something that’s easy to do anywhere else (structs, unions, classes) to mixins is just cruel and encourages people to make unreadable or inferior code because it’s the path of least resistance. Not everyone will spend a day making an enum generator like I did; and it didn’t even occur to me 2 years ago to use a mixin for enums in the case of BindBC-SDL because there was this dogma.
 BTW, your example looks fine as it is!
You said that it hurts your head to read code like that. But even if it’s ‘fine’, it would compile faster & take fewer lines if we could put conditional compilation in comma-separated lists, particularly enums and (associative) array literals. I would only need to write one array literal instead of a CTFE lambda with a series of ifs. Notice also that I did not nest those ifs, which is slightly worse for performance, but I had to preserve readability. With a single array literal, I would not have to make these compromises.
Jul 31
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
If you really want conditional enums, you can do this:

```
struct E
{
     enum A = 3;
     enum B = 4;
     version (XYZ) enum C = 5;
}

E e = E.A;
```
Aug 01
parent reply Witold Baryluk <witold.baryluk+d gmail.com> writes:
On Thursday, 1 August 2024 at 17:46:37 UTC, Walter Bright wrote:
 If you really want conditional enums, you can do this:

 ```
 struct E
 {
     enum A = 3;
     enum B = 4;
     version (XYZ) enum C = 5;
 }

 E e = E.A;
 ```
``` e2.d:8:7: error: cannot implicitly convert expression ‘A’ of type ‘int’ to ‘E’ 8 | E e = E.A; ``` Also fails to catch other issues: ```d auto e = E.A | 7; ``` Compiles, but I would prefer it didn't in this case. The closest I was able to get to something decent (using version in this example is trivial, so only showing inheritance and flag manipulation) is this example: ```d struct Typedef(T, T init = T.init, string cookie = "", alias inherit_from = T) { private T payload = init; this(T init) { payload = init; } this(typeof(this) tdef) { this(tdef.payload); } static if (!is(inherit_from : T)) { this(inherit_from tdef) { this(tdef.payload); } } auto ref opBinary(string op, this X, B)(auto ref B b) if (op != "in") { return Typedef(mixin("payload "~op~" b.payload")); } //static if (!is(inherit_from : T)) { auto ref opBinaryRight(string op, this X)(auto ref inherit_from b) { return Typedef(mixin("b.payload " ~ op ~ " payload")); } //} //auto ref opCast(T, this X)() { // return cast(T)payload; //} } struct MMap { alias Flags = Typedef!(ulong, 0, "MMap"); static const Flags None = 0; static const Flags A = 1; static const Flags B = 2; }; struct LinuxMMap { alias Flags = Typedef!(ulong, 0, "LinuxMMap", MMap.Flags); static foreach (x; __traits(allMembers, MMap)) { static if (x != "Flags") { mixin("static const LinuxMMap.Flags "~x~" = MMap."~x~";"); } } static const Flags C = 4; version (X86_64) { static const Flags D = 30; static const Flags E = 34; } else version (AArch64) { static const Flags F = 33; static const Flags G = 36; } else { static assert(0); } } auto n(T)(T flags0) { LinuxMMap.Flags flags = flags0; import std; writefln("%s %d", flags, flags.payload); } void main() { n(MMap.A | MMap.B | LinuxMMap.C); } ``` The "inheritance" part can be easily wrapped in a reusable mixin template. It detects (and rejects) constructs like `n(MMap.A | 5)`, etc. But still accepts `n(5)`, but this is fixable using some template constraints. So of course this works, and I knew it even before sending initial , but it is just quite ugly. I also did check generate assembly code, and it looks good (all inlined, and folded to a constant `7` above), at least using gdc.
Aug 04
parent Walter Bright <newshound2 digitalmars.com> writes:
It depends on just what you want an enum for. The example I gave was for a 
scoped collection of named ints, you're right it doesn't not actually create an 
enum type.
Aug 06
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 7/31/2024 7:06 PM, IchorDev wrote:
 You said that it hurts your head to read code like that. But even if it’s 
 ‘fine’, it would compile faster & take fewer lines
Not perceptibly faster (the lexer is very fast), and fewer lines is not always the best thing.
 if we could put conditional 
 compilation in comma-separated lists, particularly enums and (associative)
array 
 literals. I would only need to write one array literal instead of a CTFE
lambda 
 with a series of ifs. Notice also that I did not nest those ifs, which is 
 slightly worse for performance, but I had to preserve readability. With a
single 
 array literal, I would not have to make these compromises.
I do things like this: ``` /// Map to unsigned version of type __gshared tym_t[256] tytouns = tytouns_init; extern (D) private enum tytouns_init = () { tym_t[256] tab; foreach (ty; 0 .. TYMAX) { tym_t tym; switch (ty) { case TYchar: tym = TYuchar; break; case TYschar: tym = TYuchar; break; case TYshort: tym = TYushort; break; case TYushort: tym = TYushort; break; case TYenum: tym = TYuint; break; case TYint: tym = TYuint; break; case TYlong: tym = TYulong; break; case TYllong: tym = TYullong; break; case TYcent: tym = TYucent; break; case TYschar16: tym = TYuchar16; break; case TYshort8: tym = TYushort8; break; case TYlong4: tym = TYulong4; break; case TYllong2: tym = TYullong2; break; case TYschar32: tym = TYuchar32; break; case TYshort16: tym = TYushort16; break; case TYlong8: tym = TYulong8; break; case TYllong4: tym = TYullong4; break; case TYschar64: tym = TYuchar64; break; case TYshort32: tym = TYushort32; break; case TYlong16: tym = TYulong16; break; case TYllong8: tym = TYullong8; break; default: tym = ty; break; } tab[ty] = tym; } return tab; } (); ``` to make complex initializers. Works great! I don't know the details of your particular solution.
Aug 01
prev sibling next sibling parent reply Max Samukha <maxsamukha gmail.com> writes:
On Wednesday, 31 July 2024 at 06:11:15 UTC, Walter Bright wrote:

 version (x86_64)
 {
     enum FOO
     {
        A = 5,
        B = 6,
        C = 7,
     }
 }
 else version (AArch64)
 {
    enum FOO
    {
        A = 5,
        B = 6,
        C = 17,
    }
 }
 else
     static assert(0);
 ```

 Ah, doesn't that look nicer?
It's been said maybe a billion times why this solution rarely works in the read world. And the arguments have been dismissed in the classic D way.
Jul 31
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 01/08/2024 4:04 AM, Max Samukha wrote:
 On Wednesday, 31 July 2024 at 06:11:15 UTC, Walter Bright wrote:
 
 version (x86_64)
 {
     enum FOO
     {
        A = 5,
        B = 6,
        C = 7,
     }
 }
 else version (AArch64)
 {
    enum FOO
    {
        A = 5,
        B = 6,
        C = 17,
    }
 }
 else
     static assert(0);
 ```

 Ah, doesn't that look nicer?
It's been said maybe a billion times why this solution rarely works in the read world. And the arguments have been dismissed in the classic D way.
Yeah not supporting this, just doesn't fit D anymore. Too many people try it, which shows that it is clearly good language design. It helps to fulfill peoples ideas, so an easy win to me.
Jul 31
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/31/2024 10:51 AM, Richard (Rikki) Andrew Cattermole wrote:
 Too many people try it, which shows that it is clearly good language design.
People try all kinds of things in programming, eventually they evolve towards Best Practices.
Jul 31
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 01/08/2024 7:08 AM, Walter Bright wrote:
 On 7/31/2024 10:51 AM, Richard (Rikki) Andrew Cattermole wrote:
 Too many people try it, which shows that it is clearly good language 
 design.
People try all kinds of things in programming, eventually they evolve towards Best Practices.
Luckily we have Dscanner, so if it turns out a pattern gets too complex and therefore is not best practice, we can throw a warning at it without breaking code! Which severely diminishes any possible objections.
Jul 31
prev sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Wednesday, 31 July 2024 at 19:08:13 UTC, Walter Bright wrote:
 On 7/31/2024 10:51 AM, Richard (Rikki) Andrew Cattermole wrote:
 Too many people try it, which shows that it is clearly good 
 language design.
People try all kinds of things in programming, eventually they evolve towards Best Practices.
? Someone who starts at haskell will end up very different then someone who starts with c; to say nothing of different kinds of problems. No. If you ask 5 80 year old programmers youll get 5 different answers for whats a "best practice" we all wont "evolve" to the same answers for the foreseeable future.
Jul 31
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 7/31/2024 9:04 AM, Max Samukha wrote:
 It's been said maybe a billion times why this solution rarely works in the
read 
 world.
I know, it comes up regularly. A classic case is the antecedent where a list of operating system enum values changes with each platform. I've had the dubious pleasure of having to fix the equivalent for struct fields that vary by operating system, and were wrong because it's hard to compare a mess of conditional compilation with the operating system spec which does not have conditionals in it. Let's look at the original again: ``` enum FOO { A = 5, B = 6, version (x86_64) { C = 7, } else version (AArch64) { C = 17, } else { static assert(0); } E = 9, // fixed that! } ``` The example does follow best practice by adding the static assert for an unanticipated operating system. Now, let's add a G for AArch64: ``` enum FOO { A = 5, B = 6, version (x86_64) { C = 7, } else version (AArch64) { C = 17, } else { static assert(0); } E = 9, G = 10, } ``` Does x86_64 have a G, or not? Is the programmer going to check each supported system for G? No, they're not. Are they going to wrap it in its own version declaration? Nope. (Even if they do, it runs afoul of what to do about the else clause.) I see it again and again. I fix them again and again. Sometimes they are never caught at all. One could put G in the AArch64 block, but then it is out of order, making it hard to check them against the operating system spec which will be in order. But if the operating systems are in separate declarations, this is not an issue. The person adding G to x86_64 doesn't need to check operating systems he's not familiar with. Getting the enum values wrong results in really hard to debug failures. I know you don't find this convincing.
Jul 31
prev sibling parent reply IchorDev <zxinsworld gmail.com> writes:
On Wednesday, 31 July 2024 at 16:04:43 UTC, Max Samukha wrote:
 And the arguments have been dismissed in the classic D way.
Yes it feels a bit like a dogma, which is not really the D way IMO… we have a lot of choice about how we write code to best meet our needs. Especially with meta-programming—think of Pegged or similar.
Jul 31
parent Walter Bright <newshound2 digitalmars.com> writes:
On 7/31/2024 5:54 PM, IchorDev wrote:
 Yes it feels a bit like a dogma,
All languages have their particular dogmas. Scott Meyers wrote a popular series of "Effective C++" books which are accepted C++ dogma, for example. D tries to discourage constructs that have a troublesome history. I know that some of these constructs seem like good ideas, the trouble only appears years later.
Aug 01
prev sibling parent An Pham <home home.com> writes:
On Wednesday, 31 July 2024 at 06:11:15 UTC, Walter Bright wrote:
 This comes up from time to time.

 ```
 enum FOO {
    A = 5,
    B = 6,
 version (x86_64) {
    C = 7,
 } else version (AArch64) {
    C = 17,
 } else {
    static assert(0);
 }
    version E = 9,
 }
 ```

 I've seen this forever in C. It just makes my brain bleed. 
 Here's the D way:

 ```
 version (x86_64)
 {
     enum FOO
     {
        A = 5,
        B = 6,
        C = 7,
     }
 }
 else version (AArch64)
 {
    enum FOO
    {
        A = 5,
        B = 6,
        C = 17,
    }
 }
 else
     static assert(0);
 ```
The clean way is using "mixin" & "import" foo_windows.enum file enum FOO { A = 5, B = 6, C = 7, } foo_aarch64.enum file enum FOO { A = 5, B = 6, C = 17, } main.d file module m; version(Windows) mixin(import("foo_windows.enum")); else version(AArch64) mixin(import("foo_aarch64.enum")); else static assert(0); version(Windows) static assert(FOO.C == 7); else version(AArch64) static assert(FOO.C == 17); void main() { } build with dmd.exe -J="directory of those enum files..." main.d
Jul 31