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.
Modernes C++ Mentoring
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 |
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
Contact Me
- Mobil: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,
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).