July 01, 2022

On Friday, 1 July 2022 at 09:02:12 UTC, Ola Fosheim Grøstad wrote:

>

C and D don't have an enumeration type. enum class gave C++ a somewhat type safe enumeration type. It is very useful.

Absolutely. This is why we can write this insanity:

enum E {absolutely}
const a = E.absolutely.absolutely.absolutely.absolutely.absolutely;
July 01, 2022

On Friday, 1 July 2022 at 04:50:39 UTC, Walter Bright wrote:

>

On 6/30/2022 2:08 AM, Ogi wrote:

>

I was under impression that this is a deliberate (although questionable) design to avoid some bugs rather some technical limitation.

It is deliberate, not a bug. The rationale is the common C practice:

enum XX { XXabc, XXdef, XXghi };

where the programmer is using a workaround for a scoped name.

The proposal is not to mimic C, is to make use of the type system so we get to remove the unnecessary verbosity

If . is not the way to go, what about:

set_flags(_.FLAG_A | _.FLAG_B)

or

set_flags(with(FLAG_A | FLAG_B))

or

set_flags(auto(FLAG_A | FLAG_B))

July 01, 2022

On Thursday, 30 June 2022 at 20:32:49 UTC, ryuukk_ wrote:

>

On Thursday, 30 June 2022 at 17:15:46 UTC, ShadoLight wrote:

>

On Thursday, 30 June 2022 at 11:32:42 UTC, ryuukk_ wrote:

    with(MySuperLongName) set_my_flag( MyFlagA |  MyFlagB | MyFlagC | MyFlagD | MyFlagE | MyFlagF);

More clear at the cost of a few more characters to type. Quite acceptable compromise in my view.

Now it is harder to see the function,

At the cost of a CR you get:

    with(MySuperLongName)
    set_my_flag( MyFlagA |  MyFlagB | MyFlagC | MyFlagD | MyFlagE | MyFlagF);

For me it is pretty much equal to visually parsing a UDA on a function. But that is just me.

>

It is not nice when you just want to call a function with your enum

Maybe, but it is less ambiguous. In your scheme:

    int MyFlagA = 3;

    void main()
    {
        enum MySuperLongName {MyFlagA, MyFlagB, MyFlagC, MyFlagD, MyFlagE, MyFlagF }

        //Clearly using the enum ...
        set_my_flag( .MyFlagA |  .MyFlagB | .MyFlagC | .MyFlagD | .MyFlagE | .MyFlagF);

        //Using the enum or the global ...?
        set_my_flag( .MyFlagA );

    }

    void set_my_flag(int x){}
>

You don't do:

with(int) set_my_int(5 + 6 + 7 + 8 + 9 +10);

It kills the intent to make things simpler and logical, functions expect Enum, we give its value

Function expect int? we give a value

That is only true because of implicit conversion. If set_my_int function was declared as void set_my_int(short x), then the set_my_int(5 + 6 + 7 + 8 + 9 +10); statement will not compile (even though the value passed to x will fit in a short) and you will need to cast to short to shut the compiler up.

I would agree with you if we didn't have something like with(enumName), and needed to add the enumName on every member. But we do have the with(enumName), which is an acceptable solution to me.

Also, the fact that the .enumMember syntax is conflated with 'parent scope' and can in addition be ambiguous, makes me suspect you will need to find a better syntax to get popular support for this idea.

July 01, 2022
>

Also, the fact that the .enumMember syntax is conflated with 'parent scope' and can in addition be ambiguous, makes me suspect you will need to find a better syntax to get popular support for this idea.

Reading this thread, i might be using the wrong language and i might be expecting too much for improvements, that's unfortunate

July 01, 2022

On 7/1/22 1:28 AM, Walter Bright wrote:

>

On 6/30/2022 8:41 AM, ryuukk_ wrote:

>

     Color color = .orange; <--- it obviously pick orange, it already expect an enum

That is top-down type inference. D is designed to be bottom-up type inference. Because of overloading, having both top-down and bottom-up in play would be very confusing and likely a rich source of confusing bugs. People find overloading confusing enough already.

Yes, but this is not exactly a case without precedent e.g.:

short[] x = [1, 2, 3]; // short[]
auto y = [1, 2, 3]; // int[]

void foo(short[] arr);
foo([1, 2, 3]); // works

We do have:

auto color = Color.orange;

which has no repeats of the Color enum. However, this doesn't scale for bitwise enums:

auto mask = Enum.optionA | Enum.optionB;

I would love it for function overloading to work, and it's definitely not possible for .value to be the syntax since that's already taken. But something along these lines would be very nice.

How I would design it:

  1. We designate a syntax for "enum value", which is a polysemous literal (akin to how 1 is polysemous for int, short, byte, bool, etc.) that can be resolved as any enum type with that specific member name. For purposes of argument, let's say it's #value.

  2. If you try to assign it to a specific enum, it gets resolved to that enum's type. e.g. Color c = #orange;

  3. If you try to use it as a function parameter, it gets resolved only if there is no ambiguity.

enum A { a, b }
enum B { b, c }

void bar(int x);
void foo(A a);
void foo(B b);

foo(#a); // calls foo(A)
foo(#b); // error, ambiguous
bar(#a); // error, must be used as an enum type
  1. If it gets used as a non enum type, or an unknown type, then it's a compiler error.
enum A { a, b }

auto x = #a; // error
int y = #a; // error
void foo(T)(T t) {}
foo(#a); // error
  1. If a #value literal is used in an expression with only other #value literals that would normally result in the type of an enum, the compiler will treat the entire expression as a polysemous literal for any enum with all the given names.
enum A { a, b, c }
enum B { b, c, d }

A x = #a | #b; // ok
auto y = #a | #b; // error, #expression must be used as an enum
A z = #a | #d; // error, d is not a member of A

void foo(A a);
void foo(B b);

foo(#a | #b); // calls foo(A)
foo(#b | #c); // error, ambiguous

Why does it work? Because the type is not an enum, it's an enum expression without any type until it's used as an enum. Since all the values are compile-time, it shouldn't be a problem to generate the constant at the time the type is resolved.

Is this possible? If so, would it be allowed? No sense in working on something that has no chance of acceptance, even if it is sound.

I will say, this is one thing from Swift that I really miss in D.

-Steve

July 01, 2022
On 7/1/2022 8:42 AM, Steven Schveighoffer wrote:
> Yes, but this is not exactly a case without precedent e.g.:
> 
> ```d
> short[] x = [1, 2, 3]; // short[]
> auto y = [1, 2, 3]; // int[]
> 
> void foo(short[] arr);
> foo([1, 2, 3]); // works
> ```

That's because the initializer, *after* it is evaluated for its type, gets cast to the type of the lvalue.
July 01, 2022
On 7/1/2022 8:42 AM, Steven Schveighoffer wrote:
> How I would design it:

Those are reasonable suggestions.

But just let me throw this out. I've heard from many sources that nobody understands C++ function overloading. It is described with pages of detail in the Standard. Even compiler writers only *temporarily* understand it while they are implementing it.

How C++ programmers deal with it is they randomly try things until it works.

D's overloading is simpler than C++'s. But it gets more complex all the time. For example, named arguments make it more complicated.

To add more complexity would take a really really strong benefit.

BTW, if you really want a specific case to work:

    enum A { a, b, c }
    alias a = A.a;
    alias b = A.b;
    alias c = A.c;

This could probably be automated with metaprogramming.
July 01, 2022
On 7/1/2022 3:49 AM, user1234 wrote:
> enum E {absolutely}
> const a = E.absolutely.absolutely.absolutely.absolutely.absolutely;


Indubitably.
July 01, 2022

On 7/1/22 2:22 PM, Walter Bright wrote:

>

On 7/1/2022 8:42 AM, Steven Schveighoffer wrote:

>

Yes, but this is not exactly a case without precedent e.g.:

short[] x = [1, 2, 3]; // short[]
auto y = [1, 2, 3]; // int[]

void foo(short[] arr);
foo([1, 2, 3]); // works

That's because the initializer, after it is evaluated for its type, gets cast to the type of the lvalue.

Not really.

auto a = [1, 2, 3];
short[] b = [1, 2, 3];
auto c = cast(short[])a;
writeln(b); // [1, 2, 3]
writeln(c); // [1, 0, 2, 0, 3, 0]

casting the literal is not the same as casting a regular value of that type.

What I am talking about is a thing that has not yet been given a type, and becomes the type you need if it's a valid use. Just like an array literal -- it's a complex expression that maintains it's polysemous quality. The only difference is, an enum #value would not have a default type.

-Steve

July 01, 2022

On 7/1/22 2:32 PM, Walter Bright wrote:

>

On 7/1/2022 8:42 AM, Steven Schveighoffer wrote:

>

How I would design it:

Those are reasonable suggestions.

But just let me throw this out. I've heard from many sources that nobody understands C++ function overloading. It is described with pages of detail in the Standard. Even compiler writers only temporarily understand it while they are implementing it.

How C++ programmers deal with it is they randomly try things until it works.

This is why I'm asking the questions. I don't have experience on how to implement function overloading, and I don't know what pitfalls might be introduced from doing something like this.

>

D's overloading is simpler than C++'s. But it gets more complex all the time. For example, named arguments make it more complicated.

To add more complexity would take a really really strong benefit.

There's 2 questions to answer here:

  1. is it possible and feasible?
  2. do we want to add to the complexity of the language?

Obviously if 1 is false, it's a non-starter. And it looks like you have indirectly answered that it is possible.

So on to 2:

IMO, I think the language benefits greatly from little syntax tricks like this. foreach is a great improvement over for. omitting parentheses can cause problems, but in general makes things far more pleasant to write/read.

I also have experience using a language (Swift) which has this feature, and it's really really nice, especially when dealing with verbose enums.

When using D enums after using Swift, I get the same feeling I do when I use C# or C++ after experiencing D metaprogramming. It feels like the D compiler knows all the information to make this simple, it just is asking me to jump through a few more hoops than it should.

I am genuinely curious why this will add too much to the complexity of overloading.

The current rules are:

  1. no match
  2. match with implicit conversions
  3. match with qualifier conversion (if the argument type is qualifier-convertible to the parameter type)
  4. exact match

That shouldn't change.

The next part says:

Each argument (including any this reference) is compared against the function's corresponding parameter to determine the match level for that argument. The match level for a function is the worst match level of each of its arguments.

OK, so a #value expression would be an "exact match" of any enum type that contains all the value literals in that expression, and a a "no match" for any other situation. The rest of the rules would apply as-is, including partial ordering, etc.

Since enums are not related, they should be completely distinct in terms of partial ordering.

Am I misunderstanding something?

>

BTW, if you really want a specific case to work:

    enum A { a, b, c }
    alias a = A.a;
    alias b = A.b;
    alias c = A.c;

This could probably be automated with metaprogramming.

This doesn't scale well.

enum A { a, b, c }
alias a = A.a;
alias b = A.b;
alias c = A.c;

void bar(A a) {}

void foo()
{
   int a = 5;
   bar(a); // error;
}

--- mod2.d:
import mod1;
int a = 5;
void foo()
{
   bar(.a); // error;
}

And I also have used with statements, which mostly works, but I've come across some head-scratching problems when naming conflicts occur.

-Steve