C++23 Explicit Object Parameter for Method Chaining

The introduction of the explicit object parameter in C++23, colloquially known as "deducing this," fundamentally simplifies member function chaining by eliminating the boilerplate traditionally required for fluent interfaces. This feature allows member functions to explicitly declare the object instance as their first parameter, enabling the compiler to deduce the object's exact type, constness, and value category. As a result, developers can write single member functions that seamlessly maintain method chains across inheritance hierarchies and diverse value categories without resorting to the Curiously Recurring Template Pattern (CRTP) or duplicating overloads.

The Problem with Traditional Method Chaining

In standard member function chaining, a method returns a reference to *this (return *this;). While straightforward in simple classes, this approach breaks down in two primary scenarios:

  1. Inheritance Hierarchies: If a base class method returns Base&, calling it on a derived class instance casts the chain back to Base&. Any subsequent calls to methods exclusive to the derived class will fail to compile. To fix this prior to C++23, developers had to use CRTP or manually override and cast methods in every derived class.
  2. Value Category Handling: If a builder pattern needs to support both lvalues and rvalues (to allow move semantics when building temporary objects), developers had to duplicate functions with ref-qualifiers: one for & and one for &&.

How Explicit Object Parameters Solve the Issue

C++23 allows member functions to declare the implicit this pointer as an explicit parameter preceded by the this keyword. By making this parameter a forwarding reference, the function deduces both the precise derived type and the value category (lvalue or rvalue).

class BuilderBase {
public:
    template <typename Self>
    auto&& set_id(this Self&& self, int id) {
        // assign id...
        return std::forward<Self>(self);
    }
};

class ConcreteBuilder : public BuilderBase {
public:
    template <typename Self>
    auto&& set_name(this Self&& self, std::string name) {
        // assign name...
        return std::forward<Self>(self);
    }
};

Solving Derived Class Slicing

When an explicit object parameter is templated (this Self&& self), Self resolves to the type through which the function was actually called.

ConcreteBuilder builder;
builder.set_id(42)       // Resolves Self as ConcreteBuilder&, returns ConcreteBuilder&
       .set_name("Item"); // Valid: set_name is accessible because the chain was not sliced to BuilderBase&

Because set_id deduces self as ConcreteBuilder&, it returns a reference to ConcreteBuilder, keeping the derived context intact throughout the chain. This replaces CRTP entirely for this use case, yielding cleaner definitions and faster compile times.

Unifying Lvalue and Rvalue Chaining

The same mechanism handles temporary objects automatically. If the chain is invoked on an rvalue:

auto item = ConcreteBuilder{}
                .set_id(1)       // Self deduced as ConcreteBuilder&&
                .set_name("Temp"); // Self deduced as ConcreteBuilder&&

std::forward<Self>(self) preserves the rvalue category throughout each step of the pipeline. In pre-C++23 code, handling both lvalue and rvalue method chaining required two distinct overloads per chained method. With explicit object parameters, a single implementation handles lvalues, const lvalues, and rvalues correctly.