Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 524
Yuanyi Wu Avatar asked May 06 '26 08:05

Yuanyi Wu


1 Answers

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();
}
like image 160
sklott Avatar answered May 09 '26 00:05

sklott