各位同仁,各位编程爱好者,大家好。
今天,我们将深入探讨C++23引入的一项革命性特性——Deduced this,中文常译为“推导式 this”。这项特性旨在解决C++中长期存在的一个痛点:在成员函数中处理 const 和 non-const 重载的重复代码问题,尤其是在模板编程中,这个问题会变得异常棘手。通过一个参数,我们现在能够彻底简化模板中 const/non-const 重载的重复,从而写出更简洁、更易维护、更强大的代码。
1. 传统困境:const/non-const 重载的重复之痛
在C++中,我们经常需要为类的成员函数提供 const 和 non-const 两个版本。为什么?因为 const 成员函数表示它不会修改对象的状态,因此可以被 const 对象调用;而 non-const 成员函数则可以修改对象状态,只能被 non-const 对象调用。这是C++类型安全和正确性保证的核心机制之一。
考虑一个简单的 Point 类:
class Point {
private:
int x_;
int y_;
public:
Point(int x, int y) : x_(x), y_(y) {}
// non-const version: returns a modifiable reference
int& get_x() {
return x_;
}
// const version: returns a non-modifiable reference
const int& get_x() const {
return x_;
}
// non-const version: returns a modifiable reference
int& get_y() {
return y_;
}
// const version: returns a non-modifiable reference
const int& get_y() const {
return y_;
}
void print() const {
// ... implementation ...
}
};
在这里,get_x() 和 get_y() 都需要 const 和 non-const 两个版本。这种重复是显而易见的。对于简单的成员函数,这可能还不是大问题。但想象一下,如果函数逻辑更复杂,或者有更多的 getter 或 setter 呢?
问题随之而来:
- 代码重复: 相同的逻辑需要写两遍,除了
const限定符和返回类型可能略有不同。 - 维护成本: 如果需要修改函数内部逻辑,必须同时修改
const和non-const两个版本,稍有不慎就可能导致两者不一致,引入难以发现的bug。 - 模板编程的噩梦: 当我们进入模板编程领域时,这个问题会变得指数级恶化。
2. 模板中的重复:一个更深层次的挑战
让我们考虑一个通用的 Container 模板类,它可能提供像 operator[] 这样的访问器。
template <typename T>
class MyVector {
private:
T* data_;
size_t size_;
public:
MyVector(size_t s) : size_(s), data_(new T[s]) {}
~MyVector() { delete[] data_; }
// non-const operator[]
T& operator[](size_t index) {
if (index >= size_) { /* throw exception or assert */ }
return data_[index];
}
// const operator[]
const T& operator[](size_t index) const {
if (index >= size_) { /* throw exception or assert */ }
return data_[index];
}
// ... 其他成员函数 ...
};
这里 operator[] 同样面临 const/non-const 重载的问题。对于 MyVector<int> 这样简单的类型,这还可控。但如果 MyVector 变得更复杂,或者有更多类似的访问器,代码重复就会非常显著。
更进一步,我们不仅需要 const 和 non-const 版本,有时还需要考虑右值引用(&&)的版本。例如,一个 operator[] 可能返回一个右值引用,以便于移动语义的优化。
template <typename T>
class MyVector {
// ...
public:
// lvalue non-const
T& operator[](size_t index) & { // & qualifies this as lvalue-ref-qualified
// ...
return data_[index];
}
// lvalue const
const T& operator[](size_t index) const & { // const & qualifies this as const lvalue-ref-qualified
// ...
return data_[index];
}
// rvalue non-const (often returns T&& or a copy/move-constructed object)
T&& operator[](size_t index) && { // && qualifies this as rvalue-ref-qualified
// ...
return static_cast<T&&>(data_[index]); // Assuming T is movable
}
// rvalue const (less common, but possible)
const T&& operator[](size_t index) const && { // const && qualifies this as const rvalue-ref-qualified
// ...
return static_cast<const T&&>(data_[index]);
}
};
现在,一个简单的 operator[] 就需要四个重载!这还仅仅是针对 operator[]。如果一个类有多个这样的成员函数,代码量将呈几何级数增长。这种情况下,维护一致性简直是一场噩梦。
3. 传统解决方案及其局限性
在 Deduced this 出现之前,C++社区也尝试过一些方法来缓解这个问题,但都有其局限性。
3.1. 提取公共逻辑到私有辅助函数
这是最常见的模式。将核心逻辑提取到一个私有的非成员函数或静态成员函数中,然后 const 和 non-const 版本都调用这个辅助函数。
class Point {
private:
int x_;
int y_;
// Helper function that takes a reference to the data member
static int& get_x_impl(Point& p) {
return p.x_;
}
static const int& get_x_impl(const Point& p) {
return p.x_;
}
public:
Point(int x, int y) : x_(x), y_(y) {}
int& get_x() {
return get_x_impl(*this); // Call non-const helper
}
const int& get_x() const {
return get_x_impl(*this); // Call const helper
}
// ... similar for get_y ...
};
优点: 核心逻辑只写一遍。
缺点: 仍然需要两个 get_x_impl 重载和两个 get_x 成员函数重载,只是把重复从函数体移到了函数签名和转发调用。对于模板类,这个辅助函数也可能需要是模板,增加了复杂性。
3.2. const_cast (不推荐!)
一些开发者可能会尝试使用 const_cast 来避免 const 版本的实现。
class Point {
private:
int x_;
int y_;
public:
Point(int x, int y) : x_(x), y_(y) {}
int& get_x() {
return x_;
}
const int& get_x() const {
// DANGER: const_cast to call the non-const version, then cast back
// This is only safe if the underlying object is NOT truly const
return const_cast<Point*>(this)->get_x();
}
};
优点: 代码量看似减少。
缺点: 极其危险且容易出错。 const_cast 只能用于移除一个本来不是 const 对象的 const 属性。如果 *this 是一个真正的 const 对象,通过 const_cast 去修改它会导致未定义行为(Undefined Behavior)。这种做法违背了C++的 const 正确性原则,强烈不推荐。
3.3. CRTP (Curiously Recurring Template Pattern)
CRTP 可以用来在某些情况下减少代码重复,例如实现某个接口或mixin。但对于 const/non-const 重载,它通常不能直接简化成员函数本身的重载数量,反而会增加模板样板代码的复杂性。
// Not a direct solution for this specific problem, but sometimes mentioned in context of reducing boilerplate.
template <typename Derived>
class Base {
public:
// This doesn't help with const/non-const dispatch directly for Derived's members
// It's more about providing common functionality.
void common_method() {
static_cast<Derived*>(this)->specific_method();
}
};
class MyClass : public Base<MyClass> {
public:
void specific_method() { /* ... */ }
};
CRTP 无法直接解决 this 对象的 const 性和值类别(lvalue/rvalue)的泛化问题。
4. C++23 Deduced this:统一的解决方案
C++23 引入的 Deduced this 彻底改变了我们处理 const/non-const 以及值类别重载的方式。它允许我们将 this 指针(或者更准确地说,this 对象本身)声明为一个显式的函数参数,并且这个参数可以是模板化的。
4.1. 基本语法
Deduced this 的语法形式是在成员函数签名的参数列表的最前面添加一个特殊的参数:
RetType member_func(this PtrType self, OtherArgs...) { /* ... */ }
this: 这是一个新的关键字,表明这个参数是this对象。PtrType: 这是this对象的类型,它可以是:- 一个具体的引用类型,如
MyClass&或const MyClass&。 - 一个模板参数,如
T&或const T&。 - 最常见且最强大的形式:
auto&或auto&&,允许编译器根据调用上下文推导出this对象的准确类型和值类别。
- 一个具体的引用类型,如
self: 这是this对象的参数名,你可以在函数体内部通过self来访问对象的所有成员,就像以前通过*this一样。
4.2. Deduced this 如何工作
当一个成员函数被声明为 Deduced this 时,编译器会根据调用该成员函数的方式,推导出 self 参数的类型。例如:
- 如果
obj是一个MyClass类型的左值非const对象,那么调用obj.member_func(...)会使self被推导为MyClass&。 - 如果
obj是一个const MyClass类型的左值const对象,那么调用obj.member_func(...)会使self被推导为const MyClass&。 - 如果
obj是一个MyClass类型的右值对象(例如MyClass{} .member_func(...)),那么self可以被推导为MyClass&&。
通过这种方式,一个带有 Deduced this 参数的成员函数可以同时扮演 const、non-const、左值引用限定、右值引用限定等多个传统重载的角色,从而消除重复。
5. Deduced this 带来的简化
让我们用 Deduced this 重写 Point 类的 get_x() 方法。
class Point {
private:
int x_;
int y_;
public:
Point(int x, int y) : x_(x), y_(y) {}
// 使用 Deduced this 简化 get_x()
// self 的类型会根据调用者的 constness 和 value category 推导
// 例如:
// Point p; p.get_x() -> self is Point&, returns int&
// const Point cp; cp.get_x() -> self is const Point&, returns const int&
template <typename Self> // Self will be deduced as Point& or const Point&
decltype(auto) get_x(this Self&& self) { // using forwarding reference for self
return std::forward<Self>(self).x_;
}
// 或者更简洁的,直接使用 auto&&
decltype(auto) get_x(this auto&& self) {
return std::forward<decltype(self)>(self).x_;
}
// 对于 get_y() 也是一样
decltype(auto) get_y(this auto&& self) {
return std::forward<decltype(self)>(self).y_;
}
void print() const {
// 对于不访问或修改 this 状态的函数,Deduced this 并非强制,传统方式也可
// 但如果 print 需要根据对象 constness 行为不同,也可以用
// void print(this const auto& self) { ... }
std::cout << "Point(" << x_ << ", " << y_ << ")" << std::endl;
}
};
// 示例用法
int main() {
Point p1(10, 20);
p1.get_x() = 100; // Calls get_x(this Point& self), returns int&
std::cout << p1.get_x() << std::endl; // Output: 100
const Point p2(30, 40);
// p2.get_x() = 200; // ERROR: assignment to read-only reference, as expected
std::cout << p2.get_x() << std::endl; // Calls get_x(this const Point& self), returns const int&
Point(50, 60).get_x(); // Calls get_x(this Point&& self), returns int&&
// Note: returning int&& for a member is often okay if it's a temporary
// or if the member itself is a temporary. For member data, it's usually T& or const T&.
// Here, it would be int& because x_ is an lvalue member.
// The '&&' on self correctly reflects the temporary nature of 'Point(50,60)'.
return 0;
}
在上面的 get_x 例子中:
this auto&& self:这是最通用的形式。auto&&是一个转发引用(forwarding reference),它会完美转发this对象的const性和值类别。decltype(auto)作为返回类型:它会推导出std::forward<decltype(self)>(self).x_的准确类型。如果self是Point&,则self.x_是int&;如果self是const Point&,则self.x_是const int&。decltype(auto)确保了返回类型与访问器返回的引用类型完全匹配。
通过这一个函数,我们优雅地取代了传统的 const 和 non-const 两个版本,并且还自动处理了右值引用情况。
6. 深入探讨:值类别和模板化 this
Deduced this 的强大之处在于它能够将 this 对象的值类别(Value Category)也纳入考虑。C++中有四种主要的 this 限定符组合,对应四种调用场景:
MyClass&(lvalue non-const):obj.func()const MyClass&(lvalue const):const_obj.func()MyClass&&(rvalue non-const):MyClass{}.func()const MyClass&&(rvalue const):static_cast<const MyClass&&>(MyClass{}).func()(较少见)
在 Deduced this 之前,你需要为每一种情况编写一个重载。现在,一个 this auto&& self 就可以搞定一切。
6.1. auto&& self:完美转发 this
this auto&& self 是 Deduced this 最常用且最强大的形式。它允许 self 参数像一个通用引用一样,完美地转发 this 对象的 const 性和值类别。
template <typename T>
class MyVector {
private:
T* data_;
size_t size_;
public:
MyVector(size_t s) : size_(s), data_(new T[s]) {}
~MyVector() { delete[] data_; }
// 一个 operator[] 替代了所有四个重载!
template <typename Self> // Self will be deduced as MyVector<T>&, const MyVector<T>&, MyVector<T>&&, etc.
decltype(auto) operator[](this Self&& self, size_t index) {
if (index >= std::forward<Self>(self).size_) {
throw std::out_of_range("Index out of bounds");
}
// std::forward<Self>(self) 确保了 self 在访问其成员时保持原始的值类别
// 这对于返回 T&& 或 const T&& 是必要的
// 对于 data_[index] 本身,它通常是 T& 或 const T&
// decltype(auto) 会正确推导 data_[index] 的类型
return std::forward<decltype(self)>(self).data_[index];
}
// 注意:这里的返回类型虽然是 decltype(auto),但由于 data_ 是一个左值,
// 所以 self.data_[index] 永远是一个左值引用 (T& 或 const T&)。
// 如果想要实现 T&& 返回,需要更复杂的逻辑,例如 move-constructing/returning。
// 但对于成员变量的直接访问,T&/const T& 是最自然的。
};
int main() {
MyVector<int> vec(5);
vec[0] = 10; // Calls operator[](this MyVector<int>& self, size_t) -> self is MyVector<int>&, returns int&
std::cout << vec[0] << std::endl;
const MyVector<int> const_vec(5);
// const_vec[0] = 20; // Error: assignment to read-only reference, as expected
std::cout << const_vec[0] << std::endl; // Calls operator[](this const MyVector<int>& self, size_t) -> self is const MyVector<int>&, returns const int&
// 假设 MyVector 支持移动语义,这里可以返回 T&&
// MyVector<int>{5}[0] = 30; // 这是一个右值,operator[] 的 self 是 MyVector<int>&&
// 如果 operator[] 返回 T&&,则可以修改。
// 但由于 data_[index] 仍是 lvalue,这里仍是 int&
// 实际应用中,对于右值对象,operator[] 返回 T&& 意味着可以将元素移出,这需要更复杂的实现。
// 对于直接返回成员变量的引用,通常只关心 constness。
return 0;
}
在这个 MyVector 例子中,一个 operator[] 成员函数,通过 this auto&& self,完美地取代了传统的四个重载(T& operator[]()、const T& operator[]() const、T&& operator[]() &&、const T&& operator[]() const &&)。decltype(auto) 返回类型确保了 data_[index] 的 const 性被正确传递。
6.2. 显式指定 PtrType
虽然 auto&& 最通用,但有时我们可能希望限制哪些类型的 this 可以调用某个成员函数。例如,一个成员函数可能只对 const 对象有意义,或者只对左值对象有意义。
class Widget {
int value;
public:
Widget(int v) : value(v) {}
// 这个函数只能被 const 左值对象调用
int get_value_only_for_const_lvalue(this const Widget& self) {
return self.value;
}
// 这个函数只能被非 const 右值对象调用
int consume_value_only_for_rvalue(this Widget&& self) {
int temp = self.value;
self.value = 0; // 可以修改右值
return temp;
}
};
int main() {
Widget w(10);
// w.get_value_only_for_const_lvalue(); // Error: 'this' argument is 'Widget&', but function expects 'const Widget&'
const Widget cw(20);
std::cout << cw.get_value_only_for_const_lvalue() << std::endl; // OK
// Widget{30}.get_value_only_for_const_lvalue(); // Error: 'this' argument is 'Widget&&', but function expects 'const Widget&'
// w.consume_value_only_for_rvalue(); // Error: 'this' argument is 'Widget&', but function expects 'Widget&&'
std::cout << Widget{30}.consume_value_only_for_rvalue() << std::endl; // OK
// std::cout << Widget{30}.consume_value_only_for_rvalue() << std::endl; // Will try to use Widget{30} again, but it's already consumed (value=0) if it was the same temporary.
// If a new temporary, it's fine.
return 0;
}
通过显式指定 PtrType,我们可以更精确地控制成员函数的可用性,这在某些高级设计模式中非常有用。
6.3. Deduced this 与模板成员函数
当 Deduced this 与普通的模板成员函数结合时,它的力量会进一步放大。考虑一个需要接受任意类型参数的访问器:
template <typename T>
struct Wrapper {
T value;
Wrapper(T v) : value(std::move(v)) {}
// 一个模板成员函数,同时使用 Deduced this
// 它可以返回 T&, const T&, U&, const U& 等
template <typename U>
decltype(auto) get(this auto&& self, U T::*member_ptr) {
// self 会是 Wrapper<T>& 或 const Wrapper<T>& 等
// member_ptr 是一个指向 T 类型成员的指针
return (std::forward<decltype(self)>(self).value).*member_ptr;
}
};
struct Data {
int a;
double b;
};
int main() {
Wrapper<Data> w{ {1, 2.0} };
w.get(&Data::a) = 10;
std::cout << w.get(&Data::a) << std::endl; // Output: 10
const Wrapper<Data> cw{ {3, 4.0} };
// cw.get(&Data::a) = 20; // Error: assignment to read-only reference
std::cout << cw.get(&Data::b) << std::endl; // Output: 4
Wrapper{Data{5, 6.0}}.get(&Data::a); // self is Wrapper<Data>&&
return 0;
}
这个 get 成员函数是一个模板,它接受一个成员指针作为参数,并使用 Deduced this 来处理 Wrapper 对象的 const 性和值类别。这使得我们能够用一个函数处理多种情况:
- 对
const或non-constWrapper对象访问其内部Data的int成员。 - 对
const或non-constWrapper对象访问其内部Data的double成员。 - 并且自动处理了左值和右值
Wrapper对象。
7. Deduced this 的工作原理(概念性)
从概念上讲,Deduced this 可以被看作是编译器为我们做了很多重复的模板元编程和函数重载的工作。传统上,this 指针是一个隐式的、不可见的参数,其类型由成员函数的 const 和引用限定符决定。
例如,一个传统的 int& get_x() const { return x_; } 成员函数,在底层可以被想象成一个接受 const Point* this 和返回 const int& 的自由函数。
而 Deduced this 使得这个隐式的 this 参数变得显式化和模板化。当你写 decltype(auto) get_x(this auto&& self) 时,编译器实际上会根据调用上下文,生成或选择最合适的函数版本。
例如,当调用 my_point.get_x() 时:
my_point是Point&类型。- 编译器看到
get_x(this auto&& self)。 - 它将
auto&&推导为Point&。 - 函数体中的
std::forward<decltype(self)>(self).x_就会解析为(static_cast<Point&>(self)).x_,其类型为int&。 decltype(auto)返回类型因此推导为int&。
当调用 const_my_point.get_x() 时:
const_my_point是const Point&类型。- 编译器将
auto&&推导为const Point&。 - 函数体中的
std::forward<decltype(self)>(self).x_解析为(static_cast<const Point&>(self)).x_,其类型为const int&。 decltype(auto)返回类型因此推导为const int&。
Deduced this 并非引入了新的运行时机制,而是一种强大的编译时便利,它让编译器承担了生成和管理 this 相关重载的复杂性。
8. 实际考量与最佳实践
8.1. 何时使用 Deduced this
- 几乎总是应该使用:对于任何需要
const/non-const重载的成员函数,Deduced this都是首选。 - 模板类和模板成员函数:这是
Deduced this发挥最大作用的地方,它能够大幅简化泛型代码的编写。 - 访问器和操作符重载:
operator[],operator*,operator->等需要返回引用或指针的操作符,是Deduced this的理想应用场景。 - 需要区分左值/右值
this的场景:如果你的成员函数需要根据对象是左值还是右值来采取不同行为(例如,对右值进行资源移动),Deduced this auto&& self可以优雅地实现这一点。
8.2. 可读性与学习曲线
- 对于习惯了传统
const重载的C++开发者来说,this auto&& self语法可能需要一些时间适应。 - 一旦掌握,它会使代码更加简洁,消除大量重复,从而提高长期可读性和可维护性。
- 使用
decltype(auto)作为返回类型与Deduced this结合效果最佳,因为它能确保返回类型与self的const性和引用性相匹配。
8.3. 编译器支持
Deduced this 是C++23标准的一部分,因此你需要一个支持C++23的编译器(如GCC 13+, Clang 16+, MSVC 19.36+)。
8.4. 兼容性与迁移
Deduced this是一个可选的增强。旧的const和non-const重载仍然完全有效。- 你可以逐步将现有的重载替换为
Deduced this版本,无需一次性重构整个代码库。 - 注意,如果同时存在
Deduced this版本和传统重载,Deduced this版本可能会被优先选择,因为它通常被认为是一个更通用的模板。
9. 对比分析
下表总结了传统 const/non-const 重载与 C++23 Deduced this 之间的关键差异:
| 特性 / 方面 | Pre-C++23 const/non-const 重载 |
C++23 Deduced this |
|---|---|---|
| 代码重复 | 高 (至少 2x, 考虑右值则 4x) | 极低 (单个函数实现所有版本) |
| 模板复杂度 | 显著增加 (每个重载都需要是模板,或模板参数传递 const 信息) |
大幅简化 (单个 this auto&& self 处理所有情况) |
| 维护成本 | 高,易出错 (需同步更新多个版本) | 低,单点维护 (所有逻辑集中在一个函数中) |
| 值类别处理 | 需要显式为 & 和 && 提供限定符 (func() &, func() &&) |
通过 this auto&& self 自动处理 lvalue/rvalue 和 const/non-const |
| 可读性 | 熟悉,但重复冗长 | 简洁,但新颖,需要适应 |
| 样板代码 | 高 | 低 |
| 性能 | 相同 (编译器生成类似的代码) | 相同 (编译时特性,无运行时开销) |
| 适用场景 | 任何需要 const/non-const 区分的成员函数 |
任何需要 const/non-const 和/或 lvalue/rvalue 区分的成员函数,尤其在模板中 |
10. 复杂模板场景示例:一个 Matrix 类
让我们用一个更复杂的例子来展示 Deduced this 的强大之处:一个 Matrix 模板类,其中 operator() 用于访问矩阵元素。
Pre-C++23 版本:
template <typename T>
class Matrix {
private:
size_t rows_;
size_t cols_;
std::vector<T> data_;
public:
Matrix(size_t r, size_t c) : rows_(r), cols_(c), data_(r * c) {}
// non-const lvalue operator()
T& operator()(size_t r, size_t c) & { // & qualifier
if (r >= rows_ || c >= cols_) throw std::out_of_range("Matrix index out of bounds");
return data_[r * cols_ + c];
}
// const lvalue operator()
const T& operator()(size_t r, size_t c) const & { // const & qualifier
if (r >= rows_ || c >= cols_) throw std::out_of_range("Matrix index out of bounds");
return data_[r * cols_ + c];
}
// non-const rvalue operator() (less common for direct element access, but possible for move semantics)
// If you wanted to move out an element:
T&& operator()(size_t r, size_t c) && { // && qualifier
if (r >= rows_ || c >= cols_) throw std::out_of_range("Matrix index out of bounds");
return std::move(data_[r * cols_ + c]); // Requires T to be movable
}
// const rvalue operator() (even less common)
const T&& operator()(size_t r, size_t c) const && { // const && qualifier
if (r >= rows_ || c >= cols_) throw std::out_of_range("Matrix index out of bounds");
return std::move(data_[r * cols_ + c]);
}
// Other members like begin(), end() etc. would also need const/non-const overloads
};
可以看到,仅仅一个 operator() 就需要至少两个,甚至四个重载来覆盖所有 const 性和值类别。这使得 Matrix 类的定义变得冗长和重复。
C++23 Deduced this 版本:
#include <vector>
#include <stdexcept>
#include <iostream>
#include <utility> // For std::forward
template <typename T>
class Matrix {
private:
size_t rows_;
size_t cols_;
std::vector<T> data_;
public:
Matrix(size_t r, size_t c) : rows_(r), cols_(c), data_(r * c) {}
// 使用 Deduced this 彻底简化 operator()
// 一个函数涵盖所有 const/non-const, lvalue/rvalue 场景
template <typename Self> // Self will be deduced as Matrix<T>&, const Matrix<T>&, Matrix<T>&&, etc.
decltype(auto) operator()(this Self&& self, size_t r, size_t c) {
// 访问成员时使用 std::forward<Self>(self) 确保转发正确的 constness 和 value category
if (r >= std::forward<Self>(self).rows_ || c >= std::forward<Self>(self).cols_) {
throw std::out_of_range("Matrix index out of bounds");
}
// 返回类型 decltype(auto) 会根据 self 的类型以及 data_ 的访问方式,
// 正确推导出 T& (non-const lvalue), const T& (const lvalue), T&& (non-const rvalue, if data_ element is moved), etc.
// 由于 data_ 的元素本身是左值,因此这里通常是 T& 或 const T&。
// 如果我们真的想实现 T&&,需要更复杂的逻辑,例如返回 std::move(self.data_[...])
// 但对于直接访问,返回 T&/const T& 是最常见的。
return std::forward<Self>(self).data_[r * std::forward<Self>(self).cols_ + c];
}
// 假设我们还想提供一个用于迭代的 begin() 方法
// 同样可以使用 Deduced this 简化
decltype(auto) begin(this auto&& self) {
return std::forward<decltype(self)>(self).data_.begin();
}
decltype(auto) end(this auto&& self) {
return std::forward<decltype(self)>(self).data_.end();
}
};
int main() {
Matrix<double> m(2, 2);
m(0, 0) = 1.1;
m(0, 1) = 2.2;
m(1, 0) = 3.3;
m(1, 1) = 4.4;
std::cout << "Matrix m:" << std::endl;
for (size_t i = 0; i < 2; ++i) {
for (size_t j = 0; j < 2; ++j) {
std::cout << m(i, j) << " ";
}
std::cout << std::endl;
}
// Output:
// 1.1 2.2
// 3.3 4.4
const Matrix<double> cm(2, 2);
// cm(0,0) = 5.5; // Error: assignment to read-only reference, as expected
std::cout << "nAccessing const Matrix cm(0,0): " << cm(0,0) << std::endl; // Calls operator()(this const Matrix<double>& self, ...)
// Using begin/end with Deduced this
std::cout << "nIterating m:" << std::endl;
for (double val : m) { // Calls begin(this Matrix<double>& self) and end(this Matrix<double>& self)
std::cout << val << " ";
}
std::cout << std::endl;
std::cout << "nIterating const cm:" << std::endl;
for (double val : cm) { // Calls begin(this const Matrix<double>& self) and end(this const Matrix<double>& self)
std::cout << val << " ";
}
std::cout << std::endl;
// Example with rvalue Matrix
std::cout << "nAccessing rvalue Matrix(1,1): " << Matrix<int>(2,2)(1,1) << std::endl; // Calls operator()(this Matrix<int>&& self, ...)
// Returns int& because data_ element is lvalue
return 0;
}
通过 Deduced this,我们的 Matrix 类变得异常简洁。operator() 和 begin()/end() 成员函数都只需要一个版本,它们会根据调用 Matrix 对象的 const 性和值类别自动适应。这不仅节省了大量的代码行,更重要的是,它消除了维护多个重载时可能出现的同步错误。
11. C++23的演进,更优雅的泛型编程
Deduced this 标志着C++在泛型编程和消除样板代码方面又迈出了重要一步。它通过将 this 对象的属性提升为可推导的模板参数,极大地简化了成员函数 const/non-const 和值类别重载的编写。这一特性使得开发者能够以更少的代码,更清晰地表达意图,从而构建更健壮、更易于维护的C++系统,尤其是在处理复杂的模板结构时,其优势更是无可比拟。