C++23: Syntactic Sugar with Deducing This

The Curiously Recurring Template Pattern (CRTP) is a heavily used idiom in C++. It is similarly resistant to understanding as the classic design pattern visitor I presented in my last post: “C++23: Deducing This“.  Thanks to deducing this, we can remove the C and R from the abbreviation.

CRTP

The acronym CRTP stands for the C++ idiom Curiously Recurring Template Pattern and denotes a technique in C++ in which a class Derived is derived from a class template Base. The critical point is that Base has Derived as a template argument.

template <typename T>
class Base{
    ...
};

class Derived : public Base<Derived>{
    ...
};

CRTP is typically used to implement static polymorphism. Unlike dynamic polymorphism, static polymorphism happens at compile time and does not require an expensive run-time pointer indirection.

C++98

The following program crtp.cpp shows an idiomatic C++98-based implementation 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" << '\n';
  }
};

struct Derived1: Base<Derived1>{
  void implementation(){
    std::cout << "Implementation Derived1" << '\n';
  }
};

struct Derived2: Base<Derived2>{
  void implementation(){
    std::cout << "Implementation Derived2" << '\n';
  }
};

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';
  
}

The function template execute (1) uses static polymorphism. The member function base::interface (2) is the key to the CRTP idiom. The member function forwards to the implementation of the derived class: static_cast<derived*>(this)->implementation. This is possible because the function is not instantiated until it is called. The derived classes Derived1, Derived2 and Derived3 are defined at this point. Therefore, the function Base::interface can use the implementation of the derived classes. The member function Base::implementation (3) plays the role of a default implementation for the static polymorphism of the class Derived3 (4). 

The following screenshot shows the static polymorphism in action.

C++23

Thanks to the explicit object parameter (deducing this), the C and the R can be removed from the CRTP acronym:

The program deducingThisCRTP.cpp shows the C++23-based implementation of CRTP.

// deducingThisCRTP.cpp

#include <iostream>

struct Base{                                            // (1)
  template <typename Self>
  void interface(this Self&& self){
    self.implementation();
  }
  void implementation(){
    std::cout << "Implementation Base" << '\n';
  }
};

struct Derived1: Base{
  void implementation(){
    std::cout << "Implementation Derived1" << '\n';
  }
};

struct Derived2: Base{
  void implementation(){
    std::cout << "Implementation Derived2" << '\n';
  }
};

struct Derived3: Base{};

template <typename T>
void execute(T& base){
    base.interface();                                 // (2)
}

int main(){
  
  std::cout << '\n';
  
  Derived1 d1;                                        // (3)
  execute(d1);
    
  Derived2 d2;                                        // (4)
  execute(d2);
  
  Derived3 d3;                                        // (5)
  execute(d3);
  
  std::cout << '\n';
  
}

Thanks to the explicit object parameter (line 1), the type of the explicit object parameter can be deduced to the derived type and perfectly forwarded. For the concrete type in (2), Derived1 (3), Derived2 (4), and Derived3 (5) are used. Consequently, the corresponding virtual function implementation is called: std::forward<Self>(self).implementation(). With the current Microsoft compiler, the program can already be executed:

Recursive Lambdas

I got a comment on my last German post (https://www.heise.de/forum/heise-online/Kommentare/C-23-Deducing-This-erstellt-explizite-Zeiger/rekursive-lambdas/thread-7392252/) that I forgot the most descriptive applications of Deducing This: recursive lambdas. Honestly, I’m not so sure because most programmers have issues with recursion. Second, I’m not a fan of sophisticated lambdas. Lambdas should be concise and self-documenting.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    Let me show you various implementations of a recursively defined factorial function. Afterward, you can decide which version you prefer.

    Each factorial function calculates the factorial of 10: 3628800.

    C++98

    In C++98, you have two options. Either you use template metaprogramming with recursive instantiation or just a function call. The template metaprogram runs at compile time.

    // factorial_cpp98.cpp
    
    #include <iostream>
    
    template <unsigned int N>                                                                 
    struct Factorial{
        static int const value = N * Factorial<N-1>::value;
    };
    
    
    
    template <>                                                                     
    struct Factorial<0>{
        static int const value = 1;
    };
    
    int factorial(unsigned int n){
        return n > 0 ? n * factorial(n - 1): 1;
    }
    
    int main(){
        
        std::cout << '\n';
        
        std::cout << "Factorial<10>::value: " << Factorial<10>::value << '\n'; 
        std::cout << "factorial(10)         " << factorial(10) << '\n';
        
        std::cout << '\n';
    
    }
    

    I’m not sure with which version of factorial you would prefer. I probably use the C++20 version based on consteval.

    What’s next?

    The core language of C++23 has more features to offer than deducing this. They will be the topic of 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, 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, 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, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.

    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

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    1 reply
    1. Joerg
      Joerg says:

      The CRTP would be nicer with a concept:
      “`
      struct Base;

      template
      concept BaseImpl =
      std::derived_from<std::decay_t, Base>
      && requires(T t) { t.implementation(); };

      struct Base
      {
      template
      void interface(this Self&& self)

      “`
      Though if you have a default implementation in the base class, I unfortunately don’t know of a way to check that the subclass method really overrides that default (like the “override” keyword does with dynamic polymorphism).

      Reply

    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 *