March 18, 2012
Le 18/03/2012 02:23, Manu a écrit :
> The virtual model broken. I've complained about it lots, and people
> always say "stfu, use 'final:' at the top of your class".
>
> That sounds tolerable in theory, except there's no 'virtual' keyword to
> keep the virtual-ness of those 1-2 virtual functions I have... so it's
> no good (unless I rearrange my class, breaking the logical grouping of
> stuff in it).
> So I try that, and when I do, it complains: "Error: variable
> demu.memmap.MemMap.machine final cannot be applied to variable",
> allegedly a D1 remnant.
> So what do I do? Another workaround? Tag everything as final individually?
>
> My minimum recommendation: D needs an explicit 'virtual' keyword, and to
> fix that D1 bug, so putting final: at the top of your class works, and
> everything from there works as it should.

This problem isn't virtual by default at all. It would just flip the problem around.

It just show the need of keyword to express the opposite of final, virtual. The same problem occur with const immutable, you cannot go back to the mutable world when you use « const: » for example.
March 18, 2012
Le 18/03/2012 03:47, F i L a écrit :
> I'm a bit confused. Reading through the virtual function's docs
> (http://dlang.org/function.html#virtual-functions) it says:
>
> "All non-static non-private non-template member functions are virtual.
> This may sound inefficient, but since the D compiler knows all of the
> class hierarchy when generating code, all functions that are not
> overridden can be optimized to be non-virtual."
>

The compiler can. But ATM, it doesn't.

This is an implementation issue, not a language design issue.
March 18, 2012
Manu wrote:
> I mean it can't possibly know the complete 'final' class hierarchy, ie, the
> big picture. Anything anywhere could extend it. The codegen must assume
> such.

I still don't understand why you think this. The compiler must understand the full hierarchy it's compiling, and like I said before, there are very distinct rules as to what should get virtualed across a lib boundary.


> Are you saying someone might accidentally override something that's not
> virtual? That's what 'override' is for. If a method is final, it is a
> compile error to override in any way, you either need to make the base
> virtual, or explicitly 'override' on the spot if you want to do that.

I was saying that I see how:

    class Base { // author: Bob
        void commonNamedMethod() {}
    }

    // ~~~~~

    class Foo : Base { // author: Steve
        // didn't look at base class, before writing:
        void commonNamedMethod() {}
        // therefor didn't realize he overwrote it
    }

is a valid concern of having things default to virtual. I just don't think it would happen often, but I've been known to be wrong.


> The virtual methods are the exception, not the common case.

I don't thinks it's so black and white, and that's why I like having the compiler make the optimization. I think marking (public) methods you know for sure who's functionality you don't want overwritten is often a smaller case than methods who's functionality could *potentially* be overwritten. By letting the compiler optimize un-overwritten methods, you're giving the Class users more freedom over the grey areas without sacrificing performances.

However, I also think the level of freedom is largely dependent on the situation. Given the fact that you write highly optimized and tightly controlled core game engine code, I can see why your perspective leans towards control.

Given this specialization unbalance, I think that both virtual and final should be available.


> Explicit virtual even gives a nice informative cue to
> the programmer just how they are supposed to work with/extend something. You can clearly see what can/should to be extended.

This is a good argument. If nothing else, I think there should be a way for Class authors to specify (in a way code-completion can understand) a method attribute which marks a it as being designed to be overwritten.


> I sincerely fear finding myself false-virtual hunting on build night until
> 2am trying to get the game to hold its frame rate (I already do this in
> C++, but at least you can grep for and validate them!). Or cutting content
> because we didn't take the time required to manually scan for false
> virtuals that could have given us more frame time.

I think tool that maps hierarchy (showing override) would be best.
like: dmd -hierarchy"map.txt"


> You're welcome to it, but granted that, I have an additional fear that
> someone with your opinion is capable of writing classes in libs that I
> might really like to use, but can't, because they are a severe performance
> hazard.

I would argue that any such performance critical libraries should be tightly "finalized" in the first place. I think you're assuming the compiler can't, in good faith, optimize out virtual functions. Whereas I'm assuming it can.

March 18, 2012
On Sunday, 18 March 2012 at 13:54:20 UTC, F i L wrote:
> […] I think you're assuming the compiler can't, in good faith, optimize out virtual functions. Whereas I'm assuming it can.

Which is wrong as long as you don't do link-time optimization, and DMD probably won't in the foreseeable future. I tried to explain that above, think extending Thread, which has already been compiled into druntime, from your application (which is a bad example, because thread member method calls are most probably not performance sensitive, but you get the point).

That's just for the technical details, though, as far as the actual language design is concerned, I don't think virtual by default is an unreasonable choice.

David
March 18, 2012
On 18.03.2012 5:23, Manu wrote:
> The virtual model broken. I've complained about it lots, and people
> always say "stfu, use 'final:' at the top of your class".
>
> That sounds tolerable in theory, except there's no 'virtual' keyword to
> keep the virtual-ness of those 1-2 virtual functions I have... so it's
> no good (unless I rearrange my class, breaking the logical grouping of
> stuff in it).
> So I try that, and when I do, it complains: "Error: variable
> demu.memmap.MemMap.machine final cannot be applied to variable",
> allegedly a D1 remnant.
> So what do I do? Another workaround? Tag everything as final individually?
>
> My minimum recommendation: D needs an explicit 'virtual' keyword, and to
> fix that D1 bug, so putting final: at the top of your class works, and
> everything from there works as it should.

Following this thread and observing that you don't trust optimizer and compiler in many cases or have to double check them anyway, I have a suggestion: do virtual dispatch by hand via func-pointer table and use structs.

I'm serious, with a bit of metaprogramming it wouldn't be half bad, and as a bonus you don't have to pay for a monitor field per object as classes do, and in general less compiler magic to keep track of. You also gain the ability to fine tune their layout, the performance maniac side of yours must see the potential it brings :)

And since you have a few of virtuals anyway and keep them in constant check it should be double and be much less of a hassle then hunting down and second-guessing the compiler on every single step.

Bottom line thought: there is a point when a given feature doesn't bring significant convenience for a specific use case, it's then better to just stop pushing it over.

-- 
Dmitry Olshansky
March 18, 2012
On Sunday, 18 March 2012 at 14:27:04 UTC, David Nadlinger wrote:
> Which is wrong as long as you don't do link-time optimization, and DMD probably won't in the foreseeable future. I tried to explain that above, think extending Thread, which has already been compiled into druntime, from your application (which is a bad example, because thread member method calls are most probably not performance sensitive, but you get the point).

Also note that this applies to the general case where you get passed in an arbitrary instance only – if the place where an object is created is in the same translation unit where its methods are invoked, the compiler _might_ be able to prove the runtime type of the instance even without LTO.

David
March 18, 2012
On Sunday, 18 March 2012 at 14:46:55 UTC, David Nadlinger wrote:
> On Sunday, 18 March 2012 at 14:27:04 UTC, David Nadlinger wrote:
>> Which is wrong as long as you don't do link-time optimization, and DMD probably won't in the foreseeable future. I tried to explain that above, think extending Thread, which has already been compiled into druntime, from your application (which is a bad example, because thread member method calls are most probably not performance sensitive, but you get the point).
>
> Also note that this applies to the general case where you get passed in an arbitrary instance only – if the place where an object is created is in the same translation unit where its methods are invoked, the compiler _might_ be able to prove the runtime type of the instance even without LTO.

And thinking even more about it, devirtualization could also be performed by present-day DMD when directly generating an executable with all the modules being passed in via at the command line. This might actually be good enough for smaller projects which don't use separate libraries or incremental compilation.

David


March 18, 2012
On 3/18/12 6:37 AM, Manu wrote:
> On 18 March 2012 06:42, Andrei Alexandrescu
> <SeeWebsiteForEmail@erdani.org
> <mailto:SeeWebsiteForEmail@erdani.org>> wrote:
>     Then probably struct is what you're looking for.
>
>
> No, I definitely want a class. ref type, gc mem, etc.
> struct doesn't support virtual at all. I have 2 virtuals, this
> particular class has around 50 public methods, almost all of which are
> trivial accessors, called extremely heavily in hot loops. More similar
> classes to come.

Then perhaps it's a good idea to move accessors outside and take advantage of UFCS.

> I've never in 15 years seen a large-ish class where the majority of
> methods are virtual. Who writes code like that? It's never come up in
> my industry at least.

I consider thick interfaces and shallow hierarchies good design. An interface that's too small invites "inherit to extend" approaches and casts. The fact that Java made "extend" a keyword that really means "narrow" is quite ironic.


Andrei


March 18, 2012
On 3/18/12 6:59 AM, F i L wrote:
> Manu wrote:
>> D knows nothing about the class hierarchy when generating code, I
>> don't know how it can make that claim?
>
> How does D not know about class hierarchy when generating code? That
> doesn't make sense to me. It *has* to know to even generate code.

It knows about ancestors of each type but not about descendants.

Andrei

March 18, 2012
On 3/18/12 8:39 AM, deadalnix wrote:
> It just show the need of keyword to express the opposite of final,
> virtual. The same problem occur with const immutable, you cannot go back
> to the mutable world when you use « const: » for example.

Yah, ~const etc. have been suggested a couple of times. Helps casts too.

Andrei