C++23: Deducing This creates explicit pointers
Anyone who thinks that a major C++ standard is followed by a smaller one is wrong. C++23 offers powerful extensions to C++20. These extensions include the core language and, most importantly, the standard library. Today I’m introducing a small but very influential feature of the core language: Deducing This.
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++.
Deducing This, sometimes called an explicit object parameter, allows the implicit this pointer of a member function to be made explicit. Similar to Python, the explicit object parameter must be the first function parameter, and it’s called Self and self by convention. As of this writing, only one recent Windows compiler supports Deducing This.
struct Test {
void implicitParameter(); // implicit this pointer
void explictParameter(this Self& self); // explicit this pointer
};
This new programming technique in C++23 allows the function overload to be deduplicated based on the object’s lvalue/rvalue category and its constancy. Deducing This makes it much easier to reference a lambda or implement CRTP.
Function overload deduplication
Suppose a member function should be overloaded based on the lvalue/rvalue value category and the constancy of the calling object. That means a lot of paperwork. The member function must be overloaded four times.
Advertisement
// deducingThis.cpp
#include
struct Test {
template
void explicitCall(this Self&& self,
const std::string& text) { // (9)
std::cout (self).implicitCall(); // (10)
std::cout Lines (1), (2), (3), and (4) are the required function overloads. (1) and (2) take a non-constant and a constant lvalue, (3) and (4) a non-constant and constant rvalue. To put it simply, an lvalue is a value from which the address can be determined and an rvalue is a temporary value. (5) to (8) are the corresponding objects. This (5) allows the four overloads to be deduplicated in a member function that prefect forwards self (6) and calls implicitCall . This article goes into the finer points of Perfect Forwarding: Perfect Forwarding. The screenshot below nicely shows that the four function calls in the main function use the four different overloads of the implicitCall function.
Admittedly, the example presented was very academic. But this is now changing.
Referencing a lambda
The key idea of the Visitor pattern is to perform operations on an object hierarchy. The object hierarchy is stable in this classic pattern, but the operations can change frequently.
The visitor pattern
The following visitor.cpp program shows the visitor pattern in action.
// visitor.cpp
#include
#include
#include
class CarElementVisitor;
class CarElement { // (5)
public:
virtual void accept(CarElementVisitor& visitor) const = 0;
virtual ~CarElement() = default;
};
class Body;
class Car;
class Engine;
class Wheel;
class CarElementVisitor { // (6)
public:
virtual void visit(Body body) const = 0;
virtual void visit(Car car) const = 0;
virtual void visit(Engine engine) const = 0;
virtual void visit(Wheel wheel) const = 0;
virtual ~CarElementVisitor() = default;
};
class Wheel: public CarElement {
public:
Wheel(const std::string& n): name(n) { }
void accept(CarElementVisitor& visitor) const override {
visitor.visit(*this);
}
std::string getName() const {
return name;
}
private:
std::string name;
};
class Body: public CarElement {
public:
void accept(CarElementVisitor& visitor) const override {
visitor.visit(*this);
}
};
class Engine: public CarElement {
public:
void accept(CarElementVisitor& visitor) const override {
visitor.visit(*this);
}
};
class Car: public CarElement {
public:
Car(std::initializer_list
elements{carElements} {}
void accept(CarElementVisitor& visitor) const override {
for (auto elem : elements) {
elem->accept(visitor);
}
visitor.visit(*this);
}
private:
std::vector
};
class CarElementDoVisitor: public CarElementVisitor {
void visit(Body body) const override {
std::cout At the beginning of the main function, all parts of the car are created. After that, the engine and the car take on the carElementPrintVisitor (1) and (2). In (3) and (4) both objects are accepted by carElementDoVisitor. CarElement (5) and CarElementVisitor (6) are the abstract base classes of the object hierarchy and the operation hierarchy. The car is the most interesting component because it holds its components in a std::vector
The following screenshot shows the output of the program:
More information on this pattern is available in the article Patterns in Software Development: The Visitor Pattern. Admittedly, the visitor pattern is very understanding-resistant. This changes with C++23 thanks to the overload pattern.
Overload Pattern
The overload pattern is the modern C++ version of the visitor pattern. It combines variadic templates with std:variant and its std::visit function. Thanks to Deducing This in C++23, a lambda expression can explicitly use its implicit lambda object.
// visitOverload.cpp
#include
#include
#include
#include
template
using Ts::operator()…;
};
class Wheel {
public:
Wheel(const std::string& n): name(n) { }
std::string getName() const {
return name;
}
private:
std::string name;
};
class Body {};
class Engine {};
class Car;
using CarElement = std::variant
class Car {
public:
Car(std::initializer_list
elements{carElements} {}
template
void visitCarElements(T&& visitor) const {
for (auto elem : elements) {
std::visit(visitor, *elem);
}
}
private:
std::vector
};
overloaded carElementPrintVisitor { // (2)
[](const Body& body)
{ std::cout Car (1) represents the object hierarchy, and the two operations carElementPrintVisitor (2) and carElementDoVistor (3) represent the visitors. The lambda expressions in (4) and (5) that visit Car can reference the implicit lambda object and use it to visit the car’s concrete components: car.visitCarElement(self).
The output of visitor.cpp and visitOverload.cpp are identical:
What’s next?
The Curiously Recurring Template Pattern (CRTP) is a commonly used idiom in C++. It is similarly difficult to understand as the classic design Pattern Visitor. Thanks to Deducing This we can remove the C and R from the abbreviation in C++23. (rm)
To home page