Skip to Content
Course content

95: Deducing this (C++23)

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've spent any significant time writing C++ classes, you've probably felt the annoyance of "const-overload duplication." You know exactly what I'm talking about: you write a getter that returns a reference to a member, and then you realize you have to copy-paste that entire function and add const to the signature and the return type just so it works for const objects. It's boring, redundant, and a breeding ground for typos.

C++23 finally gives us a way out of this with "Deducing this." Essentially, it allows us to make the object instance an explicit parameter of the member function. Instead of relying on the implicit this pointer, we name the object ourselves.

The redundancy we're trying to kill

Let's look at a simple DataBuffer class. Normally, if I want a method to return a reference to the internal data, I'd do this:

struct DataBuffer {
    std::vector<int> data;

    // Non-const version
    std::vector<int>& get_data() { 
        return data; 
    }

    // Const version - almost identical, but necessary
    const std::vector<int>& get_data() const { 
        return data; 
    }
};

It's a tiny example, but imagine this across a massive codebase with complex return types. It's a waste of space. With deducing this, we can collapse these into a single template function.

Introducing the explicit object parameter

To do this, we move the const qualifier from the end of the function to the first parameter. We use a template for that first parameter to "deduce" what the object actually is (whether it's a const reference, a volatile reference, or an rvalue). I usually name this parameter self because it's clearer than this.

struct DataBuffer {
    std::vector<int> data;

    template <typename Self>
    auto& get_data(this Self&& self) {
        return self.data;
    }
};

Notice the this Self&& self syntax. That this keyword in the parameter list is the magic. It tells the compiler: "This isn't a normal argument; it's actually the object this function was called on."

Wait, where did 'this' go?

Here is where I tripped up the first time I used this feature. I wrote the function, but out of habit, I tried to access the member variable using this ->data inside the body.

// WRONG: I tried this first
template <typename Self>
auto& get_data(this Self&& self) {
    return this ->data; // Compiler error!
}

The compiler complained immediately. When you use deducing this, the implicit this pointer effectively disappears for that function. You must use the explicit parameter you defined—in this case, self. It feels weird for the first ten minutes, but it's actually more consistent once you get used to it.

Polishing the forwarding

The example above works, but to make it truly robust—especially if we want to handle rvalues (temporary objects) efficiently—we should use std::forward. This ensures that if the DataBuffer is a temporary, we can move from it if necessary, or maintain its const-ness if it's a const reference.

#include <vector>
#include <utility>

struct DataBuffer {
    std::vector<int> data;

    template <typename Self>
    decltype(auto) get_data(this Self&& self) {
        // We forward 'self' to preserve its value category (lvalue/rvalue)
        return std::forward<Self>(self).data;
    }
};

I switched the return type to decltype(auto) here. This is the "pro move" when combined with deducing this. It tells C++ to deduce the return type exactly as it is, including references. Now, if I call get_data() on a const DataBuffer, I get a const std::vector<int>&. If I call it on a mutable one, I get a mutable reference. One function, all use cases.




📋 Practical Task

Implement a Const-Aware String Wrapper

Build a class called StringWrapper that encapsulates a std::string. Instead of writing two separate get_value() methods (one const and one non-const), implement a single method using deducing this.

Your implementation must meet these requirements:

  • The get_value() method should use this Self&& self as its first parameter.
  • Use decltype(auto) as the return type to ensure that const-ness is preserved.
  • In your main() function, demonstrate the following:
    1. Create a mutable StringWrapper and verify you can modify the string returned by get_value().
    2. Create a const StringWrapper and verify that the string returned by get_value() is read-only (this will be a compile-time check).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.