11 comments

[ 2.7 ms ] story [ 28.1 ms ] thread
Nice overview, it misses other kinds of dispatch though.

With concepts, templates and compile time execution, there is no need for CRTP, and in addition it can cover for better error messages regarding what methods to dispatch to.

I wonder if I still have the link.

One of the papers I had bookmarked when toying with my own language design was someone that had worked out how to make interfaces as fast or faster than vtables by using perfect hashing and using the vtable as a hash table instead of a list.

You can also, when inlining a polymorphic call, put a conditional block in that bounces back to full dispatch if the call occasionally doesn’t match the common case. The problem with polymorphic inlining though is that it quickly resembles the exact sort of code we delete and replace with polymorphic dispatch:

    if (typeof arg1 == “string”) {
    } else if typeof arg1 === …) {
    } else if {
    } else if {
    } else {
    }
As someone who's favorite language is C, I don't see what is wrong with that code? Sure, you need to extend it with a new subtype, but you also need to implement every virtual function anyway. And if you use switch instead of an if-else-chain the compiler will complain when you are missing a subtype.
Good article, rare to see simple explanations of intricate C++ ideas.
> Under the hood, a virtual table (vtable) is created for each class, and a pointer (vptr) to the vtable is added to each instance.

Coming from C++ I assumed this was the only way but Rust has an interesting approach where the single objects do not pay any cost because virtual dispatch is handled by fat pointers. So you carry around the `vptr` in fat pointers (`&dyn MyTrait`) only when needed, not in every instance.

> only when needed

Do you know how is this exactly deduced?

While this is a great article, I feels it buries the lede.

For me, the key insight was from the last paragraph of the article:

C++23 introduces "deducing this", which is a way to avoid the performance cost of dynamic dispatch without needing to use tricks like CRRT, by writing:

    class Base {
    public:
      auto foo(this auto&& self) -> int { return 77 + self.bar(); }
    };

    class Derived : public Base {
    public:
      auto bar() -> int { return 88; }
    };
I wish the article had gone into more details on how this works and when you can use it, and what its limitations are.
Crazy web design, by the way. Diggin' it very much.
Since std::variant was introduced I use inheritance and virtual calls much less than before. It's faster, since variant dispatch (via std::visit) is basically a switch statement with all execution paths visible to the compiler and thus inlining is possible. Inheritance and virtual calls are nowadays only necessary in places where it's not possible to statically list all alternatives (where the set of derived classes is open).
An expressive combination is Static Polymorphism + Multiple Dispatch, which Julia resorts to when it can.