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);   // double

Practical 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 rvalue

2. 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 type

When 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

CompilerMinimum VersionStatus
GCC13.0+Supported
Clang17.0+Supported
MSVCVS 2022 17.4+Supported