Why can't we derive struct's?

Steven Schveighoffer schveiguy at gmail.com
Fri Dec 21 22:23:20 UTC 2018


On 12/21/18 4:46 PM, Walter Bright wrote:
> On 12/21/2018 11:08 AM, Steven Schveighoffer wrote:
>>> The issues I've seen in bugzilla are how alias this overrides default 
>>> expected 
>> behavior of classes. In other words, if you have a base class and want 
>> to cast it to a derived, and the base class has an alias this, it 
>> stupidly tries the alias this, and won't even consider a dynamic cast.
>>
>> It's not madness so much as poor implementation. Saying there are bugs 
>> in the alias this implementation is not the same as saying the feature 
>> is problematic. All that is needed is a good definition of what takes 
>> precedence -- we already have that, it's just not implemented properly.
> 
> The ones I reviewed do not have easy answers as to what the correct 
> behavior "ought" to be, and it certainly is not spec'ed in the 
> specification.

Which ones?

Fundamentally, alias this brings behavior for types where default 
behavior doesn't exist. For classes, casting to void * or another object 
is defined, so alias this should not override. Principal of least 
surprise. That's the only bug I've seen.

>> But MI is not multiple alias-this.
> 
> It fundamentally is. (Though I agree it doesn't have the virtual base 
> "diamond inheritance" issue.)

In terms of which alias this is used, we have leeway to define whatever 
we want. Alias this acts like inheritance, but it's NOT inheritance. We 
can say there is a specific order of precedence between the alias this'd 
items (I wouldn't recommend that), or we could say more than one alias 
this being usable in the same expression is an ambiguity error.

I don't think the precedence rules are that complicated:

1. The type's members or base classes
2. The type's alias this'd members. Any ambiguities are a compiler error.
3. UFCS functions that accept the type or base classes.
4. UFCS functions that accept any alias this'd type. Any ambiguities are 
a compiler error.

Unlike MI, there is a specific member you can use to disambiguate, which 
is way less clunky than MI, where you need a cast, or to name the type 
itself.

My vision:

void foo(int x) { writeln("foo int"); }
void foo(T t) { writeln("foo T"); }

void bar(int x) { writeln("bar int"); }

void baz(int x) { writeln("baz int"); }
void baz(T t) { writeln("baz T"); }
void baz(S s) { writeln("baz S"); }

void foobar(int x) { writeln("foobar int"); }
void foobar(T t) { writeln("foobar T"); }

struct T
{
    void bar() { writeln("bar T"); }
}

struct S
{
    int x;
    T t;
    alias this x;
    alias this t;
    void foo() { writeln("S");}
}

S s;
s.foo(); // foo S
s.bar(); // bar T; members take precedence over UFCS
s.baz(); // baz S; main type takes precedence over alias this'd
s.foobar(); // Error, both alias this'd have same precednece for UFCS
s.x.foobar(); // foobar int
s.t.foobar(); // foobar T

Don't have the "both alias this'd have the same member" error, but you 
get the idea.

And your example from earlier:

class C : B
{
   B b;
   alias b this;
}

Basically, the alias this b is never used, because C will always take 
precedence.

If you think any of this is unintuitive, please let me know.

-Steve


More information about the Digitalmars-d mailing list