90

How can I use CRTP in C++ to avoid the overhead of virtual member functions?

Lightness Races in Orbit
  • 358,771
  • 68
  • 593
  • 989

5 Answers5

142

There are two ways.

The first one is by specifying the interface statically for the structure of types:

template <class Derived>
struct base {
  void foo() {
    static_cast<Derived *>(this)->foo();
  };
};

struct my_type : base<my_type> {
  void foo(); // required to compile.
};

struct your_type : base<your_type> {
  void foo(); // required to compile.
};

The second one is by avoiding the use of the reference-to-base or pointer-to-base idiom and do the wiring at compile-time. Using the above definition, you can have template functions that look like these:

template <class T> // T is deduced at compile-time
void bar(base<T> & obj) {
  obj.foo(); // will do static dispatch
}

struct not_derived_from_base { }; // notice, not derived from base

// ...
my_type my_instance;
your_type your_instance;
not_derived_from_base invalid_instance;
bar(my_instance); // will call my_instance.foo()
bar(your_instance); // will call your_instance.foo()
bar(invalid_instance); // compile error, cannot deduce correct overload

So combining the structure/interface definition and the compile-time type deduction in your functions allows you to do static dispatch instead of dynamic dispatch. This is the essence of static polymorphism.

Dean Michael
  • 3,336
  • 1
  • 18
  • 14
  • 6
    I'd like to emphasise that `not_derived_from_base` is not derived from `base`, nor is it derived from `base`... – leftaroundabout Feb 24 '12 at 12:27
  • 3
    Actually, the declaration of foo() inside my_type/your_type is not required. codepad.org/ylpEm1up (Causes stack overflow) -- Is there a way to enforce a definition of foo at compile time? -- Ok, found a solution: ideone.com/C6Oz9 -- Maybe you want to correct that in your answer. – cooky451 Mar 03 '12 at 18:35
  • 3
    Could you explain to me what is the motivation to use CRTP in this example? If bar would be defined as template void bar(T& obj) { obj.foo(); }, then any class that provides foo would be fine. So based on your example it looks like the sole use of CRTP is to specify the interface at compile time. Is that what is it for? – Anton Daneyko Apr 16 '13 at 17:27
  • 1
    @Dean Michael Indeed the code in the example compiles even if foo is not defined in the my_type and your_type. Without those overrides the base::foo is recursively called (and stackoverflows). So maybe you want to correct you answer as cooky451 showed? – Anton Daneyko Apr 16 '13 at 17:37
  • @mezhaka: Yes, Dean Michael's example is incomplete because it could be implemented more concisely without CRTP, as you show. But add a `template bar(base2 &obj) { obj.quux(); }` -- i.e. a second base class with a different `bar()` implementation -- and the utility of CRTP becomes apparent. – Nemo Sep 18 '14 at 17:36
18

I've been looking for decent discussions of CRTP myself. Todd Veldhuizen's Techniques for Scientific C++ is a great resource for this (1.3) and many other advanced techniques like expression templates.

Also, I found that you could read most of Coplien's original C++ Gems article at Google books. Maybe that's still the case.

fizzer
  • 12,963
  • 8
  • 37
  • 60
  • @fizzer I have read the part you suggest, but still do not understand what does the template double sum(Matrix& A); buys you in comparison to template double sum(Whatever& A); – Anton Daneyko Apr 16 '13 at 17:45
  • @AntonDaneyko When called on a base instance, the sum of the base class is called, e.g. "area of a shape" with default implementation as if it were a square. The goal of CRTP in this case is to resolve the most-derived implementation, "area of a trapezoid" etc. while still being able to refer to the trapezoid as a shape until derived behavior is required. Basically, whenever you would normally need `dynamic_cast` or virtual methods. – John P Oct 22 '17 at 18:44
1

I had to look up CRTP. Having done that, however, I found some stuff about Static Polymorphism. I suspect that this is the answer to your question.

It turns out that ATL uses this pattern quite extensively.

Roger Lipscombe
  • 81,986
  • 49
  • 214
  • 348
0

CRTP/SFINAE Static Dispatching with Strict Signature Checking

This solution for static dispatching uses CRTP and SFINAE, which is not new. What is unique about this solution is that it also enforces strict signature checking, which allows us to statically dispatch overloaded methods in the same way dynamic dispatching works for virtual functions.

To begin, let's first look at the limitations of a traditional solution using SFINAE. The following was taken from Ben Deane's CppCon 2016 Lightning Talk “A Static Alternative to Virtual Functions, Using Expression SFINAE."

#define SFINAE_DETECT(name, expr)                                       \
  template <typename T>                                                 \
  using name##_t = decltype(expr);                                      \
  template <typename T, typename = void>                                \
  struct has_##name : public std::false_type {};                        \
  template <typename T>                                                 \
  struct has_##name<T, void_t<name##_t<T>>> : public std::true_type {};

// detect CommonPrefix(string)
SFINAE_DETECT(common_prefix,
              declval<T>().CommonPrefix(std::string()))

Using the above code, the template instantiation has_complete<DerivedClass> will, in general, do what you would expect. If DerivedClass has a method named Complete that accepts a std::string, the resulting type will be std::true_type.

What happens when you want to overload a function?

template <class Derived>
struct Base {
    std::string foo(bool);
    std::string foo(int);
    ...
};

struct Derived : public Base<Derived>
{
    std::string foo(int);
};

In this case, Derived does, in fact, have a method named foo that accepts a bool because bool is implicitly convertible to int. Therefore, even if we only set up dispatching for the signature that accepts a bool, has_foo<Derived> would resolve to std::true_type, and the call would be dispatched to Derived::foo(int). Is this what we want? Probably not, because this is not the way that virtual functions work. A function can only override a virtual function if the two signatures match exactly. I propose that we make a static dispatch mechanism that behaves in the same way.

template <template <class...> class Op, class... Types>
struct dispatcher;

template <template <class...> class Op, class T>
struct dispatcher<Op, T> : std::experimental::detected_t<Op, T> {};

template <template <class...> class Op, class T, class... Types>
struct dispatcher<Op, T, Types...>
  : std::experimental::detected_or_t<
    typename dispatcher<Op, Types...>::type, Op, T> {};

template <template <class...> class Op, class... Types>
using dispatcher_t = typename dispatcher<Op, Types...>::type;

That's nice, but that alone doesn't enforce signature checks. To perform strict signature checking, we have to properly define the template template parameter Op. To do this, we will make use of a std::integral_constant of a member function pointer. Here's what that looks like:

template <class T>
using foo_op_b = std::integral_constant<std::string(T::*)(bool), &T::foo>;

template <class T>
using foo_op_i = std::integral_constant<std::string(T::*)(int), &T::foo>

Defining our Ops in this way allows us to dispatch only to methods with an exact signature match.

// Resolves to std::integral_constant<std::string(T::*)(bool), &Derived::foo>
using foo_bool_ic = dispatcher_t<foo_op_b, Derived, Defaults>;

// Resolves to std::integral_constant<std::string(T::*)(int), &Defaults::foo>
using foo_int_ic = dispatcher_t<foo_op_i, Derived, Defaults>;

Now let's put it all together.

#include <iostream>
#include <experimental/type_traits>
#include <string>

template <template <class...> class Op, class... Types>
struct dispatcher;

template <template <class...> class Op, class T>
struct dispatcher<Op, T> : std::experimental::detected_t<Op, T> {};

template <template <class...> class Op, class T, class... Types>
struct dispatcher<Op, T, Types...>
  : std::experimental::detected_or_t<
    typename dispatcher<Op, Types...>::type, Op, T> {};

template <template <class...> class Op, class... Types>
using dispatcher_t = typename dispatcher<Op, Types...>::type;


// Used to deduce class type from a member function pointer
template <class R, class T, class... Args>
auto method_cls(R(T::*)(Args...)) -> T;


struct Defaults {
    std::string foo(bool value) { return value ? "true" : "false"; }
    std::string foo(int  value) { return value ? "true" : "false"; }

    // Ensure that the class is polymorphic so we can use dynamic_cast
    virtual ~Defaults() {};
};

template <class Derived>
struct Base : Defaults {
    template <class T>
    using foo_op_b = std::integral_constant<std::string(T::*)(bool), &T::foo>;

    template <class T>
    using foo_op_i = std::integral_constant<std::string(T::*)(int), &T::foo>;

    std::string foo(bool value) {
        auto method = dispatcher_t<foo_op_b, Derived, Defaults>::value;
        auto *target = dynamic_cast<decltype(method_cls(method)) *>(this);
        return (target->*method)(value);
    }

    std::string foo(int value) {
        auto method = dispatcher_t<foo_op_i, Derived, Defaults>::value;
        auto *target = dynamic_cast<decltype(method_cls(method)) *>(this);
        return (target->*method)(value);
    }
};

struct Derived : Base<Derived> {
    std::string foo(bool value) { return value ? "TRUE" : "FALSE"; }
};

int main() {
    Derived d;
    std::cout << dynamic_cast<Base<Derived> *>(&d)->foo(true) << std::endl; // TRUE
    std::cout << dynamic_cast<Base<Derived> *>(&d)->foo(1) << std::endl;    // true
}

Writing a macro that creates a dispatcher for a non-overloaded member function would be simple enough, but making one that supports overloaded functions would be a bit more challenging. If anybody cares to contribute that, I'd welcome the addition.

jisrael18
  • 331
  • 8
-5

This Wikipedia answer has all you need. Namely:

template <class Derived> struct Base
{
    void interface()
    {
        // ...
        static_cast<Derived*>(this)->implementation();
        // ...
    }

    static void static_func()
    {
        // ...
        Derived::static_sub_func();
        // ...
    }
};

struct Derived : Base<Derived>
{
    void implementation();
    static void static_sub_func();
};

Although I don't know how much this actually buys you. The overhead of a virtual function call is (compiler dependent, of course):

  • Memory: One function pointer per virtual function
  • Runtime: One function pointer call

While the overhead of CRTP static polymorphism is:

  • Memory: Duplication of Base per template instantiation
  • Runtime: One function pointer call + whatever static_cast is doing
user23167
  • 475
  • 5
  • 12
  • 4
    Actually, the duplication of Base per template instantiation is an illusion because (unless you still have a vtable) the compiler will merge the storage of the base and the derived into a single struct for you. The function pointer call is also optimized out by the compiler (the static_cast part). – Dean Michael Nov 04 '08 at 18:52
  • 19
    By the way, your analysis of CRTP is incorrect. It should be: Memory: Nothing, as Dean Michael said. Runtime: One (faster) static function call, not virtual, which is the whole point of the exercise. static_cast doesn't do anything, it just allows the code to compile. – Frederik Slijkerman Nov 05 '08 at 08:26
  • 2
    My point is that the base code will be duplicated in all template instances (the very merging you talk of). Akin to having a template with only one method that relies on the template parameter; everything else is better in a base class otherwise it is pulled in ('merged') multiple times. – user23167 Nov 05 '08 at 16:37
  • 1
    Each *method* in the base will be compiled again for each derived. In the (expected) case where each instantiated method is different (because of the properties of Derived being different), that can't necessarily be counted as overhead. But it can lead to larger overall code size, vs the situation where a complex method in the (normal) base class calls virtual methods of subclasses. Also, if you put utility methods in Base, which don't actually depend at all on , they will still get instantiated. Maybe global optimization will fix that somewhat. – greggo Nov 01 '16 at 17:39
  • A call that goes through several layers of CRTP will expand in memory during compilation but can easily contract through TCO and inlining. CRTP itself isn't really the culprit then, right? – John P Oct 22 '17 at 19:15