C++23: Syntactic Sugar with Deducing This
The Curiously Recurring Template pattern (CRTP) is a commonly used idiom in C++. It’s similarly hard to understand as the classic Viistor design pattern I introduced in my last article, “C++23: Deducing This Creates Explicit Pointers”. Thanks to Deducing This we can remove the C and R from the abbreviation.
Advertisement
Rainer Grimm has been working as a software architect, team leader and training manager for many years. He likes to write articles on the programming languages C++, Python and Haskell, but also likes to speak frequently at specialist conferences. On his blog Modernes C++ he deals intensively with his passion for C++.
CRTP
The acronym CRTP stands for the C++ idiom Curiously Recurring Tplate Pattern and designates a technique in C++ in which a class Derived is derived from a class template Base. The key point is that Base has Derived as a template argument.
template
class Base{
…
};
class Derived : public Base
…
};
CRTP is typically used to implement static polymorphism. In contrast to dynamic polymorphism, this takes place at compile time and does not require any expensive pointer indirection.
C++98
Advertisement
The following crtp.cpp program presents an idiomatic implementation of CRTP based on C++98.
// crtp.cpp
#include
template
struct Base{
void interface(){ // (2)
static_cast
}
void implementation(){ // (3)
std::cout {
void implementation(){
std::cout {
void implementation(){
std::cout {}; // (4)
template
void execute(T& base){
base.interface();
}
int main(){
std::cout The function template execute (1) uses static polymorphism. The Base::interface(2) member function is the key of the CRTP idiom. The member function forwards the call to the derived class’s implementation: static_cast
C++23
Thanks to the explicit object parameter, the C and the R can be removed from the acronym CRTP.
The deducingThisCRTP.cpp program introduces the C++23-based implementation of CRTP.
// deducingThisCRTP.cpp
#include
struct Base{ // (1)
template
void interface(this Self&& self){
self.implementation();
}
void implementation(){
std::cout
void execute(T& base){
base.interface(); // (2)
}
int main(){
std::cout The parameters of the Explicit object make it possible to infer the derived type and to forward it perfectly (1). For the specific type in (2), Derived1 (3), Derived2 (4), and Derived3 (5) are used. Consequently, the corresponding Virtual Function implementation called: std::forward
Recursive Lambdas
I received a comment on my last German post that I forgot the most illustrative uses of Deducing This: Recursive Lambdas. To be honest, I’m not so sure if this is the best use of Deducing This, because most programmers have trouble with recursion. Second, I’m not a fan of complicated lambdas. Lambdas should be concise and self-documenting.
Now I present different implementations of a recursively defined factorial function. After that, everyone can decide for themselves which version is the easiest to read.
Each 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 you use a function call. The template metaprogram is run at compile time.
// factorial_cpp98.cpp
#include
template
struct Factorial{
static int const value = N * Factorial
};
template
struct Factorial{
static int const value = 1;
};
int factorial(unsigned int n){
return n > 0 ? n * factorial(n – 1): 1;
}
int main(){
std::cout ::value: ”
::value In the case of the template metaprogram, a full template specialization for the values 2 to 10 is created:
C++11
In C++11, the factorial function can be constexpr and has the potential to be run at compile time.
// factorial_cpp11.cpp
#include
constexpr int factorial(unsigned int n){
return n > 0 ? n * factorial(n – 1): 1;
}
int main(){
std::cout C++17
Thanks to constexp if, different code is generated depending on whether N > 0 or not. As with the template metaprogram in C++11, the compiler creates fully specialized templates for values 2 through 10:
// factorial_cpp17.cpp
#include
template
constexpr int factorial() {
if constexpr (N > 0)
return N * factorial
else
return 1;
}
int main(){
std::cout () ” () A constexpr function (C++11) has the potential to run at compile time, but a consteval function (C++20) must run at compile time.
C++20
// factorial_cpp20.cpp
#include
consteval int factorial(unsigned int n){
return n > 0 ? n * factorial(n – 1): 1;
}
int main(){
std::cout Finally I ended up in C++23. In C++23, a lambda can refer to itself. This allows me to implement a recursive lambda.
C++23
// factorial_cpp23.cpp
#include
auto factorial = [](this auto&& self, unsigned int n) -> int {
return n > 0 ? n * self(n – 1): 1;
};
int main(){
std::cout The MSVC compiler does not yet fully support this deducing this. I therefore need to specify the return type (-> int) of the recursive lambda. According to proposal P0847R7, this is not necessary.
auto factorial = [](this auto&& self, unsigned int n) {
return n > 0 ? n * self(n – 1): 1;
};
Here is the output of the program:
Everyone may prefer a different variant of the factorial function. My favorite is the C++20 version based on consteval.
What’s next?
The core C++23 language offers more features than Deducing This. Exactly these features will be the subject of my next article. (rm)
To home page