Deducing This (C++23)
Explicit object parameters for perfect forwarding
The CRTP Problem
Before C++23, writing functions that work with both const and non-const objects required verbose workarounds like CRTP or multiple overloads.
// C++20 and earlier: Multiple overloads needed
class Widget {
int value_;
public:
// Non-const version
int& value() { return value_; }
// Const version (duplicated logic!)
const int& value() const { return value_; }
// Also need rvalue versions for perfect forwarding...
};
// Or use CRTP (complex, hard to understand)
template<typename Derived>
class Mixin {
// Access derived type via static_cast
};C++23: Explicit Object Parameter
C++23 allows this to be an explicit parameter, deducing the object's value category.
// C++23: Single function handles all cases
class Widget {
int value_;
public:
// 'Self' deduces whether we're called on lvalue/rvalue, const/non-const
template<typename Self>
auto&& value(this Self&& self) {
return std::forward<Self>(self).value_;
}
};
// All these work with one definition:
Widget w;
w.value(); // int&
const Widget cw;
cw.value(); // const int&
Widget{}.value(); // int&&
std::move(w).value(); // int&&How It Works
Deduction
this Self&& is a forwarding reference. Self deduces the cv-qualifiers and value category of the object.
Forwarding
std::forward<Self>(self) preserves the original value category when accessing members.
Recursive Lambdas
One of the most popular uses is creating self-referential lambdas:
// C++23: Recursive lambda without std::function overhead
auto fib = [](this auto&& self, int n) -> int {
if (n <= 1) return n;
return self(n - 1) + self(n - 2);
};
std::cout << fib(10); // 55
// Works with any lambda, including generic
auto factorial = [](this auto&& self, auto n) {
if (n <= 1) return decltype(n){1};
return n * self(n - 1);
};
factorial(5); // int
factorial(5.0); // doublePractical Examples
1. Fluent Interface (Method Chaining)
class Builder {
std::string name_;
int value_ = 0;
public:
// Returns the same value category as *this
template<typename Self>
auto&& set_name(this Self&& self, std::string name) {
self.name_ = std::move(name);
return std::forward<Self>(self);
}
template<typename Self>
auto&& set_value(this Self&& self, int value) {
self.value_ = value;
return std::forward<Self>(self);
}
};
// Chaining works with all value categories
auto obj = Builder()
.set_name("test")
.set_value(42); // Returns rvalue2. Proxy Objects
template<typename Container>
class Proxy {
Container& container_;
size_t index_;
public:
Proxy(Container& c, size_t i) : container_(c), index_(i) {}
// Proxy can be used as lvalue or rvalue
template<typename Self>
auto&& get(this Self&& self) {
return std::forward<Self>(self).container_[self.index_];
}
// Assignment works through proxy
template<typename T>
Proxy& operator=(T&& value) {
container_[index_] = std::forward<T>(value);
return *this;
}
};3. CRTP Replacement
// Old CRTP way (complex)
template<typename Derived>
class Comparable {
bool operator==(const Comparable& other) {
return static_cast<const Derived&>(*this).value() ==
static_cast<const Derived&>(other).value();
}
};
// New deducing this way (simple)
class Comparable {
int value_;
public:
template<typename Self, typename Other>
bool operator==(this const Self& self, const Other& other) {
return self.value_ == other.value_;
}
};Member Function Pointer Quirks
Functions with explicit object parameter have different types:
class Widget {
public:
// Regular member function
void regular();
// Deducing this function
template<typename Self>
void deducing(this Self&& self);
};
// Different function pointer types:
void (Widget::*regular_ptr)() = &Widget::regular;
// Deducing this is not a member function pointer!
// It's essentially a free function with hidden first parameter
auto deducing_ptr = &Widget::deducing; // Complex typeWhen to Use
- ✓Replace multiple const/non-const overloads
- ✓Recursive lambdas
- ✓Fluent interfaces with proper value category preservation
- ✓Proxy objects that preserve reference semantics
- ⚠May complicate function pointers and virtual functions
Compiler Support
| Compiler | Minimum Version | Status |
|---|---|---|
| GCC | 13.0+ | Supported |
| Clang | 17.0+ | Supported |
| MSVC | VS 2022 17.4+ | Supported |