No we should not support enum types derived from strings

Andrei Alexandrescu SeeWebsiteForEmail at erdani.org
Thu May 13 04:21:38 UTC 2021


On 5/12/21 9:41 PM, Paul Backus wrote:
> On Thursday, 13 May 2021 at 01:16:42 UTC, Andrei Alexandrescu wrote:
>> It seems you can do almost everything with an enum that you can do 
>> with its base type. Keyword being "almost". For example,
>>
>> x ~= "asd";
>>
>> works whether x is a string or an enum based on string. However,
>>
>> x = x ~ "asd";
>>
>> works if x is a string and does not work if x is an enum derived from 
>> string. Therefore, a function using that expression works for strings 
>> but not for enum strings.
> 
> A template function, you mean? Because (as the rest of the post you 
> quoted demonstrates) the LSP does not and has never applied (in D) to 
> substitutions that involve different instantiations of the same 
> template. If you explicitly instantiate `func!string`, then it will work 
> exactly as the LSP dictates, but if you substitute `func!string(x)` with 
> `func!E(x)`, you have no guarantee.
> 
> Granted, the fact that `x ~= "asd"` works and `x = x ~ "asd"` doesn't is 
> definitely a bug.

Well the problem is that the choice of covariance of results for 
operations on enums vs their "base" is quite arbitrary.

For strings, the result of "~" is not covariant but the result of "~=" 
is - not only it works, but it returns a reference to the enum type, not 
the base type.

However, for enums derived from integrals the result of "+" is not 
covariant when adding an enum with an integral, but covariant when two 
enums are added together. Same goes for "-", "/", "*", but oddly not for 
"^^". I suspect nobody thought of trying to raise an enum to the power 
of an enum.

The plot thickens when considering enums derived from user-defined types:

void main() {
     import std;
     struct S {
         void fun() { writefln("%s", &this); }
         int min = -1;
     }
     enum X : S { x = S() }
     X x;
     x.fun;
     (cast(S*) &x).fun;
     writeln(x.min);
}

The two addresses are the same, meaning the enum value gets to call the 
base member's function, in a subtyping manner. However, the last line 
doesn't compile, which breaks subtyping.

On the face of it, enums are defined by the language, so whatever 
choices are made are... there. I understand the practicality of some 
choices, but overall the entire enum algebra is quirky and difficult to 
maneuver around in generic code. Which harkens back to the opener of 
this thread - Phobos should not go out of its way to support enumerated 
types everywhere, when a trivial recourse exists on the caller side - 
pass value.representation instead of value.

A much stronger argument could be made against supporting convertibility 
(to e.g. strings or ranges) by means of alias this. Callers should 
convert to the needed type prior to calling into the standard library.


More information about the Digitalmars-d mailing list