Home » C++23: Deducing This creates explicit pointers

C++23: Deducing This creates explicit pointers

by admin
C++23: Deducing This creates explicit pointers

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.

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.

See also  AMD Ryzen 7045 / 7040 and Radoen 7600M XT / 7700S Laptop CPUs and GPUs Updated Simultaneously

Admittedly, the example presented was very academic. But this is now changing.

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 carElements ):
elements{carElements} {}

void accept(CarElementVisitor& visitor) const override {
for (auto elem : elements) {
elem->accept(visitor);
}
visitor.visit(*this);
}
private:
std::vector elements; // (7)
};

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 (7). The key observation of the visitor pattern is that what operation is performed depends on two objects: the visitor and the visited object.

The following screenshot shows the output of the program:

See also  Idioms in software development: polymorphism and templates

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 struct overloaded : Ts… {
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 carElements ):
elements{carElements} {}

template
void visitCarElements(T&& visitor) const {
for (auto elem : elements) {
std::visit(visitor, *elem);
}
}
private:
std::vector elements;
};

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:

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

You may also like

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More

Privacy & Cookies Policy