C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
95: Deducing this (C++23)
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 usethis Self&& selfas its first parameter. - Use
decltype(auto)as the return type to ensure that const-ness is preserved. - In your
main()function, demonstrate the following:- Create a mutable
StringWrapperand verify you can modify the string returned byget_value(). - Create a
const StringWrapperand verify that the string returned byget_value()is read-only (this will be a compile-time check).
- Create a mutable
There are no comments for now.