模版的特化引发的权限扩大的解决方法
一:引发权限扩大的场景
假设现在有一个仿函数,用来替代 <小于 的比较功能,如下
template<class T>
class less
{
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
现在我们传两个int,或者两个double,就能调用在另一个类中调用仿函数
新增需求:能比较两个int*指向的内容的大小,且不能舍弃比较普通类型的大小的功能
所以:既要比较int*指向的内容的大小,也要能比较两个int的大小,那模版的特化就能轻松的解决
特化代码:
template<class T>
class less
{
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
//模版的特化 针对指针类型
template<>
class less<T*>
{
bool operator()(const T*& x, const T*& y)
{
return *x < *y;
}
};
错误思维:既然是针对指针的特化 ,那我们特化的代码中的 T全部替换成T*应该就可以了吧
此时:当我们想当然的传递两个int*除法仿函数的特化的时候,会产生以下报错
为什么会产生权限扩大的报错呢??
二:错误的本质
①:权限问题
解释:C/C++中,权限缩小和平移视为合理的,而权限扩大会报错
a:a是一个const修饰int类型的变量(可读不可写),不能被一个正常的int类型的变量b所引用(可读可写),这是权限的扩大
b: c是一个int类型的变量(可读可写),能被一个正常的int类型的变量d所引用(可读可写),这是权限的平移
c:e是一个int类型的变量(可读可写),能被一个const修饰的int类型的变量f所引用(可读不可写),这是权限的缩小
好比:你是一个村长,你可以管理村里事务,也可以不管理,但你不可能管理县里的事务
总结:权限放大会报错,缩小平移合理
注意:
解释:这是对的,是合理的!
会有人认为e是cosnt的,而f是正常的,权限放大了每一个报错
int f = e;
并没有涉及权限,而是直接进行了值拷贝(即 e
的值 30
被直接赋给了 f
),只是简单地把 e
的值(30
)拷贝到 f
,并保证 f
不能被修改。
②:类型转换会生成具有常性的临时变量
解释:为什么double前面加上了const就行了?
因为 int
到 double 是不同类型,会触发类型转换,而类型转换会生成一个具有常属性(可读不可写)的临时变量,此时b和d都是对这个常属性临时变量进行引用,而按照①权限问题可以,权限只能缩小和平移,所以既然你这个临时变量是只可读不可写,那我只能用const修饰的变量d来就接收你,因为const修饰的变量也是只可读不可写
总结:类型转换时,左操作数应该被const修饰
Q:你怎么知道类型转换会生成一个临时变量?
A:下图可解释
解释:b得到了a的临时变量转换而来的10,所以a本身还是一个double的值,不受影响
③:const修饰的类型和普通类型 是两种类型 可能会涉及类类型转换
在 C++ 中,const int
和 int
是不同的类型,但通常它们之间的赋值或初始化是隐式兼容的(因为 const
只是增加限制,而不是改变底层类型)。不过,在涉及 引用、指针或模板 时,(也就是博客中所展示的这种场景的时候)它们的区别会更明显。我们可以通过以下代码验证它们确实是不同的类型,并在某些情况下触发类型转换:
1. 模板类型推导(const int
和 int
是不同的类型)
#include <iostream>
#include <type_traits>
template<typename T>
void checkType() {
if (std::is_same_v<T, int>) {
std::cout << "Type is int" << std::endl;
}
else if (std::is_same_v<T, const int>) {
std::cout << "Type is const int" << std::endl;
}
else {
std::cout << "Other type" << std::endl;
}
}
int main() {
int a = 10;
const int b = 20;
checkType<decltype(a)>(); // 输出:Type is int
checkType<decltype(b)>(); // 输出:Type is const int
return 0;
}
输出:
Type is int
Type is const int
总结:
int
和 const int
在模板推导时被视为不同的类型。
④:对特化参数的解读
Q: const T*& x中的const修饰的是什么? 假设为const int*& x
A:const修饰的 *x ,表示指针指向的内容不能被修改,也就是说int* 是被const修饰的
问题来了:
我们调用仿函数的时候,传参是两个int*的时候,我们想当然的去比较指针指向的内容。
此时参数是一个被const修饰的int*,而实参是int*,这是两种类型,会发生类型转换,按照前文所言,会生成一个具有常属性(可读不可写)的临时变量,然后const T*& x 中的 &,作用是让x去引用这个临时变量,而x呢,没被const修饰,直接去引用一个常属性的临时变量,权限扩大,则报错!!
解决方法:
让x也被const修饰就行了,所以参数变为const T*cosnt & x 即可,第一个const修饰*x(指针指向的内容),第二个const修饰x,以让x能够正确的引用一个常属性的临时变量
图解:
总结:偏特化的时候 对于指针不建议加引用
指针不加引用也没啥,指针大小最大8字节,而加了引用,则需要双const
三:易混淆点
我们权限扩大中的例子中的权限缩小的例子,会触发类型转换吗,生成临时变量吗?
int e = 30;
const int& f = e; // ✅ 正确:隐式生成临时变量(但优化后可能直接绑定)
解释:
优化:现代编译器通常会直接让 f
引用 e
(因为 e
本身是可修改的,const
只是限制通过 f
修改 e
),不会真正生成临时变量。