网站首页 > 技术文章 正文
模板访问权限的放宽
当使用嵌套类(定义在类内部的类)进行模板特化或偏特化时,如果这个嵌套类的访问权限是私有或者保护时,按照以前的 C++ 语法标准,是没有权限进行访问的。但这样的代码在开发模板库时是很常用的,通过对嵌套类使用 trait 技巧来实现对不同类型的模板的特定策略。
由于这种用法很常用,所以很多编译器都默许这样的代码编译通过。所以在 C++20 中,对这种用法进行了规范,要么明确允许这种用法,明确不受嵌套类的权限影响,要么是增加模板的友元(不是具体类的友元)。经过评估,友元的方案增加不必要的复杂性,最后还是采用第一种方案,明确允许这种用法,简化代码。
嵌套类的模板特化和偏特化的例子如下:
// C++20 新特性(24):模板访问权限和typename的放宽
#include <iostream>
using std::cout, std::endl;
// <0> 辅助类,用于推导模板参数
template<class T>
struct extract_value_type
{
typedef T value_type;
};
template< template<typename> class X, class T >
struct extract_value_type< X<T> >
{
typedef T value_type;
};
template<class T>
struct trait
{
int f() { return 1; }
int g() { return 2; }
};
class CA
{
private:
class CB
{
public:
int h() { return 10; }
};
template<class U> struct Impl
{
int h() { U u1; return u1.h() + 100; }
};
int f() { return 3; }
public:
int g();
int h() { return 9; }
};
// <1> 模板特化,使用CA::CB私有嵌套类进行特化,C++20明确允许访问
template<> struct trait< CA::CB >
{
// int f() { CA::CB u1; return u1.h() + 4; } // <1-2> Error,可以特化这个嵌套类作为参数的模板类,但不代表在特化实现中可以使用这个嵌套类,除非设置了友元
int f() { typename extract_value_type< trait >::value_type u1; return u1.h() + 4; } // <1-3> OK,通过辅助类推导模板参数,则可以在特化实现中使用私有的嵌套类
int g() { return 5; }
};
// <2> 模板偏特化,使用CA::Impl私有嵌套模板类进行特化,C++20明确允许访问
template<class U>
struct trait< CA::Impl<U> >
{
int f() { CA::Impl<U> u1; return u1.h() + 6; } // <2-2> 偏特化中,可以使用私有的嵌套类模板,只是模板,并没有实例化具体的类
int g() { return 7; }
};
int CA::g()
{
// <3> 使用<1>中的特化类,因为CA的类成员函数可以访问CB
trait< CB > b1;
int c1 = b1.f();
int c2 = b1.g();
// <4> 使用<2>中的特化类,因为CA的类成员函数可以访问Impl模板,也可以访问Impl<CB>这个实例化的类
trait< Impl<CB> > b2;
int c3 = b2.f();
int c4 = b2.g();
printf( "%d %d %d %d\n", c1, c2, c3, c4 ); // 输出 14 5 116 7
return 8;
}
int main( int argc, char * argv[] )
{
CA a;
a.g();
// trait<CA::CB> b; // <5> Error,定义模板特化时可以不管访问权限,但实例化模板时还是要检查访问权限
// trait< CA::Impl<CA> > c; // <6> Error,定义模板偏特化时可以不管访问权限,但实例化模板时还是要检查访问权限
return 0;
}
在特化的模板实现中,不能直接使用模板参数的私有嵌套类,但可以通过模板推导的技巧,间接使用模板参数的私有嵌套类,而在偏特化中,则是可以直接使用的。
但是到了通过这个模板类定义具体的变量时,还是会受到访问权限的限制。
模板中放宽对 typename 的使用
在模板开发中,有时候无法简单地判断一个和模板参数相关的表达式,到底是一个类型还是一个变量,因此需要添加typename关键字来明确说明这个表达式是一个类型。
但有些地方,实际上是只能出现类型的,但按照以前的C++标准,还是要求明确添加typename关键字来说明这个表达式是一个类型,这样造成开发模板时要写太多的typename。因此在C++20中,对这些地方进行放宽,明确只能出现类型的地方,可以不写typename。
另外,对于本地定义的类型,也明确不需要加typename。
具体例子如下:
// C++20 新特性(24):模板访问权限和typename的放宽
#include <iostream>
#include <string>
using std::cout, std::endl;
struct CA
{
typedef std::string TYPE;
};
template<class T>
/* typename */ T::TYPE f1() // <1> 这里明确是需要一个类型的地方,所以不用写typename
{
typename T::TYPE a; // <2> 这里不是必须出现类型的地方,所以需要明确写typename
// T::TYPE * a2; // <3> 不写typename,会将T::TYPE当做一个变量,和a2相乘,
typedef typename T::TYPE TY3; // <4> 这里不是必须出现类型的地方,所以需要明确写typename
TY3 a3; // <5> 本地typedef出来的类型,可以直接使用,不需要加typename
using TY4 = T::TYPE; // <6> 这里明确是需要一个类型的地方,所以不用写typename
TY4 a4;
void g1( typename T::TYPE ); // <7> 这里不是必须出现类型的地方,所以需要明确写typename,表示定义一个函数
// std::string g2( T::TYPE ); // <8> 不加typename,就当做值使用,表示定义一个变量并通过另一个值来初始化,但实际不是一个值而是类型,所以编译失败
return a;
}
int main( int argc, char * argv[] )
{
CA::TYPE a1 = f1<CA>();
return 0;
}
- 上一篇: C++零基础到工程实践
- 下一篇: 如何使用C语言编程实现一个推箱子游戏?技术核心和算法实现
猜你喜欢
- 2025-08-02 C|在一个结构体嵌套一个共用体实现一体多用
- 2025-08-02 C++中,常用的强制类型转换函数
- 2025-08-02 如何使用C语言编程实现一个推箱子游戏?技术核心和算法实现
- 2025-08-02 C++零基础到工程实践
- 2025-05-14 “Rust真能防住C代码里的那些老问题吗?我们做了个实验验证”
- 2025-05-14 C语言连续生成不同的随机数方法实例加程序
- 2025-05-14 C++20尝鲜:新增语法糖
- 2025-05-14 C 语言的整数提升
- 2025-05-14 C语言之位运算符
- 2025-05-14 CSP-J 2024 信奥赛入门组第一轮初赛真题及答案解析(C++)
- 08-02C|在一个结构体嵌套一个共用体实现一体多用
- 08-02C++中,常用的强制类型转换函数
- 08-02如何使用C语言编程实现一个推箱子游戏?技术核心和算法实现
- 08-02C++20 新特性(24):模板访问权限和typename的放宽
- 08-02C++零基础到工程实践
- 08-02[深度学习] Python人脸识别库face_recognition使用教程
- 08-02AI算法之怎么利用Python实现支持向量机SVM算法
- 08-02【机器学习】SVM支持向量机
- 1521℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 623℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 526℃MySQL service启动脚本浅析(r12笔记第59天)
- 492℃启用MySQL查询缓存(mysql8.0查询缓存)
- 491℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 479℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 460℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 458℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- windowsscripthost (69)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- c语言min函数头文件 (68)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)