StaticDynamic

More about Dynamic and Static Polymorphism

In my last post, “Dynamic and Static Polymorphism“, I introduced dynamic polymorphism. Today, I continue with static polymorphism and present a very interesting idiom in C++: curiously recurring template pattern (CRTP).

 

A short recap. This is where I left in my last post.

Dynamic Polymorphism is based on object orientation and enables us to separate between the interface and the implementation of a class hierarchy. To get late dynamic dispatch, you need two ingredients: virtuality and an indirection such as a pointer or a reference. The following program exemplified dynamic polymorphism:

// dispatchDynamicPolymorphism.cpp

#include <chrono>
#include <iostream> auto start = std::chrono::steady_clock::now(); void writeElapsedTime(){ auto now = std::chrono::steady_clock::now(); std::chrono::duration<double> diff = now - start; std::cerr << diff.count() << " sec. elapsed: "; } struct MessageSeverity{ virtual void writeMessage() const { std::cerr << "unexpected" << '\n'; } }; struct MessageInformation: MessageSeverity{ void writeMessage() const override { std::cerr << "information" << '\n'; } }; struct MessageWarning: MessageSeverity{ void writeMessage() const override { std::cerr << "warning" << '\n'; } }; struct MessageFatal: MessageSeverity{}; void writeMessageReference(const MessageSeverity& messServer){ // (1) writeElapsedTime(); messServer.writeMessage(); } void writeMessagePointer(const MessageSeverity* messServer){ // (2) writeElapsedTime(); messServer->writeMessage(); } int main(){ std::cout << '\n'; MessageInformation messInfo; MessageWarning messWarn; MessageFatal messFatal; MessageSeverity& messRef1 = messInfo; MessageSeverity& messRef2 = messWarn; MessageSeverity& messRef3 = messFatal; writeMessageReference(messRef1); writeMessageReference(messRef2); writeMessageReference(messRef3); std::cerr << '\n'; MessageSeverity* messPoin1 = new MessageInformation; MessageSeverity* messPoin2 = new MessageWarning; MessageSeverity* messPoin3 = new MessageFatal; writeMessagePointer(messPoin1); writeMessagePointer(messPoin2); writeMessagePointer(messPoin3); std::cout << '\n'; }

 

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • Do you want to stay informed: Subscribe.

     

    Static polymorphism is based on templates. Refactor the program using the Curiously Recurring Template Pattern (CRTP).

    Static Polymorphism

    Before I refactor the previous program dispatchDynamicPolymorphism.cpp, here is the key idea of CRTP: A class Derived derives from a class template Base and Base has Derived as a template argument.

    template <typename T>
    class Base
    {
        ...
    };
    
    class Derived : public Base<Derived>
    {
        ...
    };
    

     

    Here is the pure nature of CRTP:

    // crtp.cpp
    
    #include <iostream>
    
    template <typename Derived>
    struct Base{
      void interface(){                              // (2)
        static_cast<Derived*>(this)->implementation();
      }
      void implementation(){                        // (3)
        std::cout << "Implementation Base" << std::endl;
      }
    };
    
    struct Derived1: Base<Derived1>{
      void implementation(){
        std::cout << "Implementation Derived1" << std::endl;
      }
    };
    
    struct Derived2: Base<Derived2>{
      void implementation(){
        std::cout << "Implementation Derived2" << std::endl;
      }
    };
    
    struct Derived3: Base<Derived3>{};             // (4)
    
    template <typename T>                          // (1)
    void execute(T& base){
        base.interface();
    }
    
    
    int main(){
      
      std::cout << '\n';
      
      Derived1 d1;
      execute(d1);
        
      Derived2 d2;
      execute(d2);
      
      Derived3 d3;
      execute(d3);
      
      std::cout << '\n';
      
    }
    

     

    I use in the function template execute (line 1) static polymorphism. Each base invoked the method base.interface. The member function Base::interface (line 2) is the key point of the CRTP idiom. The member function dispatches to the implementation of the derived class: static_cast<Derived*>(this)->implementation().  That is possible because the method will be instantiated when called. At this point in time the derived classes Derived1, Derived2, and Derived3 are fully defined. Therefore, the method Base::interface can use the implementation of its derived classes. Pretty interesting is the member function Base::implementation (line 3). This function plays the role of a default implementation for the static polymorphism for the class  Derived3 (line 4).

    Here is the output of the program:

     crtp

    Now, let me take the next step and refactor the program dispatchDynamicPolymorphism.cpp.

    // dispatchStaticPolymorphism.cpp
    
    #include <chrono>
    #include <iostream>
    
    auto start = std::chrono::steady_clock::now();
    
    void writeElapsedTime(){
        auto now = std::chrono::steady_clock::now();
        std::chrono::duration<double> diff = now - start;
      
        std::cerr << diff.count() << " sec. elapsed: ";
    }
    
    template <typename ConcreteMessage>                        // (1)
    struct MessageSeverity{
      void writeMessage(){                                     // (2)
        static_cast<ConcreteMessage*>(this)->writeMessageImplementation();
      }
      void writeMessageImplementation() const {
        std::cerr << "unexpected" << std::endl;
      }
    };
    
    struct MessageInformation: MessageSeverity<MessageInformation>{
      void writeMessageImplementation() const {               // (3)
        std::cerr << "information" << std::endl;
      }
    };
    
    struct MessageWarning: MessageSeverity<MessageWarning>{
      void writeMessageImplementation() const {               // (4)
        std::cerr << "warning" << std::endl;
      }
    };
    
    struct MessageFatal: MessageSeverity<MessageFatal>{};     // (5)
    
    template <typename T>
    void writeMessage(T& messServer){                       
    	
        writeElapsedTime();                                   
        messServer.writeMessage();                            // (6)
    	
    }
    
    int main(){
    
        std::cout << std::endl;
      
        MessageInformation messInfo;
        writeMessage(messInfo);
        
        MessageWarning messWarn;
        writeMessage(messWarn);
    	
        MessageFatal messFatal;
        writeMessage(messFatal);
      
        std::cout << std::endl;
    
    }
    

     

    In this case, all concrete classes (lines 3, 4, and 5) derive from the base class MessageSeverity. The member function writeMessage is the interface that dispatches to the concrete implementations writeMessageImplementation. To achieve this, the object will be upcasted to the ConcreteMessage:  static_cast<ConcreteMessage*>(this)->writeMessageImplementation();. This is the static dispatch at compile time and coined the name for this technique: static polymorphism.

    It took me time to get used to it, but applying the static polymorphism in line (6) is quite easy.

    In the end, I want to compare dynamic and static polymorphism in a few words:

    Dynamic Versus Static Polymorphism

    Dynamic polymorphism happens at run time, and static polymorphism at compile time. Dynamic polymorphism typically requires a pointer indirection at run time (read the post “Demystifying virtual functions, Vtable, and VPTR in C++“), but static polymorphism has no performance costs at run time. Admittedly, there is a reason why the idiom curiously recurring template pattern (CRTP) has the name curious inside. For beginners, the idiom is quite challenging to understand. So, what should you use?

    First of all, don’t overestimate the costs of a virtual dispatch. In most cases, you can ignore them. Read the excellent paper “Technical Report on C++ Performance” for details. It’s pretty dated but has in section 5.3.3 exciting numbers about the additional costs of virtual function calls. If you are still concerned about performance, there is only one cure: measure. Put your performance tests under version control. Always rerun them if something in your setup consisting of your hardware, compiler, or compiler version changes because this invalidates your previous performance numbers.

    In the end, code is way more often read the written. Therefore, you should use the techniques your team is most comfortable with.  

    What’s next?

    Mixins are a widespread technique in Python. They allow you to change the behavior of a class using multiple inheritances. Thanks to CRTP, we also have mixins in C++. Read about them in my next post.

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, moon, Philipp Lenk, Hobsbawm, and Charles-Jianye Chen.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Seminars

    I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

    Standard Seminars (English/German)

    Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

    • C++ – The Core Language
    • C++ – The Standard Library
    • C++ – Compact
    • C++11 and C++14
    • Concurrency with Modern C++
    • Design Pattern and Architectural Pattern with C++
    • Embedded Programming with Modern C++
    • Generic Programming (Templates) with C++
    • Clean Code with Modern C++
    • C++20

    Online Seminars (German)

    Contact Me

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *