c++polymorphismvirtual-functionscrtpstatic-polymorphism

CRTP vs. virtual function as an interface or mixin


I wonder if there is any benefit of using CRTP over virtual function polymorphism if I never invoke the function from the base class (i.e., virtual dispatch)?

Here are the sample code. The disassembly can be found at https://godbolt.org/z/WYKaG5bbG.

struct Mixin {
  virtual void work() = 0;
};

template <typename T>
struct CRTPMixin {
  void call_work() {
    static_cast<T*>(this)->work();
  }
};

struct Parent {};
struct Child : Parent, Mixin, CRTPMixin<Child> {
  int i = 0;
  void work() override {
    i ++;
  }
};

Child child;
Mixin& mixin = child;


int main() {
  child.work();
  mixin.work();
  child.call_work();
}

I found that if I call the virtual function work from the child or through the CRTPMixin interface, the disassembly code are the same, with only static call. If I call the function on Mixin& mixin = child the virtual dispatch occurs and there are more instructions generated for this operation.

My question is, if I am designing the interface/mixin type struct, which I will only call with the derived class, not the base class, is there any case where CRTP will benefit more than the virutal function method?

Thanks!


Solution

  • If you will always call from derived class only, then CRTP is much better than virtual functions. Not only it is faster to call functions directly, than through virtual dispatch, but it also allow function inlining and other optimizations.

    And starting with C++23 we can do CRTP even simpler than before. example from https://en.cppreference.com/w/cpp/language/crtp

    #include <cstdio>
     
    #ifndef __cpp_explicit_this_parameter // Traditional syntax
     
    template <class Derived>
    struct Base { void name() { (static_cast<Derived*>(this))->impl(); } };
    struct D1 : public Base<D1> { void impl() { std::puts("D1::impl()"); } };
    struct D2 : public Base<D2> { void impl() { std::puts("D2::impl()"); } };
     
    void test()
    {
        Base<D1> b1; b1.name();
        Base<D2> b2; b2.name();
        D1 d1; d1.name();
        D2 d2; d2.name();
    }
     
    #else // C++23 alternative syntax; https://godbolt.org/z/KbG8bq3oP
     
    struct Base { void name(this auto& self) { self.impl(); } };
    struct D1 : public Base { void impl() { std::puts("D1::impl()"); } };
    struct D2 : public Base { void impl() { std::puts("D2::impl()"); } };
     
    void test()
    {
        D1 d1; d1.name();
        D2 d2; d2.name();
    }
     
    #endif
     
    int main()
    {
        test();
    }