On Saturday, 23 November 2019 at 11:21:32 UTC, Ola Fosheim Grøstad wrote:
However, virtual functions can be slow.
[...]
(so you don't have to require Heap allocation with classes)?

Is your worry in slowness of virtual functions or heap allocation?

Neither is necessarily a problem with classes. The compiler can optimize out a lot of virtual calls, and heap allocation is pretty easily avoided in your own code (and in theory the compiler could optimize that too, but it doesn't as far as I know right now).

Take a look at this code:

---

class Base {
        void doLotsOfStuff() {
                import std.stdio;
                writeln("I am doing lots of stuff.");
                writeln(childResult());
                writeln("cool");
        }

        int defaultValue() { return 5; }

        abstract int childResult();
}

final class Child : Base {
        override int childResult() { return defaultValue() + 5; }
}

void workOnIt(T : Base)(T obj) {
        obj.doLotsOfStuff();
}

void main() {
        import std.typecons;
        auto c = scoped!Child; // on-stack store
Child obj = c; // get the reference back out for template function calls

        workOnIt(obj);
}

---


The three lines in main around scoped make it a stack class instead of a heap one.

The `final` keyword on the Child class tells the compiler it is allowed to aggressively devirtualize interior calls from it (in fact, ldc optimizes that `childResult` function into plain `return 10;`) and inline exterior calls to it.

`workOnIt` can similarly optimized automatically in many cases, even a a plain function if it gets inlined, but here I showed a template. If the optimizer fails on the plain function, trying a template like this can allow that `final` to propagate even further. In my test, it simply inlined the call.

The only function here that didn't get devirtualized is the abstract method itself, childResult. All the surrounding stuff was though.


If you want to get even that devirtualized.... there's the curiously-recurring template pattern to make it happen.

----
// base is now a template too
class Base(T) {
        void doLotsOfStuff() {
                import core.stdc.stdio; // I switched to printf cuz cleaner asm
                printf("I am doing lots of stuff.\n");

                // and this cast enables static dispatch...
                printf("%d\n", (cast(T) this).childResult());
                printf("cool\n");
        }

        int defaultValue() { return 5; }

        abstract int childResult();
}

// child inherits base specialized on itself, so it can see final
// down in the base as well as on the outside
final class Child : Base!(Child) {
        override int childResult() { return defaultValue() + 5; }
}

void workOnIt(T : Base!T)(T obj) {
        obj.doLotsOfStuff();
}

void main() {
        import std.typecons;
        auto c = scoped!Child; // on-stack store
        Child obj = c; // get the reference back out

        workOnIt(obj);
}
----


It depends just how crazy you want to go with it, but I think this is less crazy than trying to redo it all with mixins and structs - classes and interfaces do a lot of nice stuff in D. You get attribute inheritance, static checks, and familiar looking code to traditional OO programmers.

BTW the curiously-recurring template pattern is fun because you also get static reflection through it! I wrote a little about this in my blog a few months ago: http://dpldocs.info/this-week-in-d/Blog.Posted_2019_06_10.html


If you really wanna talk about structs specifically, I can think about that too, just I wouldn't write off classes so easily! I legit think they are one of the most underrated D features in this community.

Reply via email to