模板访问权限的放宽
当使用嵌套类(定义在类内部的类)进行模板特化或偏特化时,如果这个嵌套类的访问权限是私有或者保护时,按照以前的 C++ 语法标准,是没有权限进行访问的。但这样的代码在开发模板库时是很常用的,通过对嵌套类使用 trait 技巧来实现对不同类型的模板的特定策略。
由于这种用法很常用,所以很多编译器都默许这样的代码编译通过。所以在 C++20 中,对这种用法进行了规范,要么明确允许这种用法,明确不受嵌套类的权限影响,要么是增加模板的友元(不是具体类的友元)。经过评估,友元的方案增加不必要的复杂性,最后还是采用第一种方案,明确允许这种用法,简化代码。
嵌套类的模板特化和偏特化的例子如下:
// C++20 新特性(24):模板访问权限和typename的放宽
#include
using std::cout, std::endl;
// <0> 辅助类,用于推导模板参数
template
struct extract_value_type
{
typedef T value_type;
};
template< template class X, class T >
struct extract_value_type< X >
{
typedef T value_type;
};
template
struct trait
{
int f() { return 1; }
int g() { return 2; }
};
class CA
{
private:
class CB
{
public:
int h() { return 10; }
};
template 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
struct trait< CA::Impl >
{
int f() { CA::Impl 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这个实例化的类
trait< Impl > 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 b; // <5> Error,定义模板特化时可以不管访问权限,但实例化模板时还是要检查访问权限
// trait< CA::Impl > c; // <6> Error,定义模板偏特化时可以不管访问权限,但实例化模板时还是要检查访问权限
return 0;
}
在特化的模板实现中,不能直接使用模板参数的私有嵌套类,但可以通过模板推导的技巧,间接使用模板参数的私有嵌套类,而在偏特化中,则是可以直接使用的。
但是到了通过这个模板类定义具体的变量时,还是会受到访问权限的限制。
模板中放宽对 typename 的使用
在模板开发中,有时候无法简单地判断一个和模板参数相关的表达式,到底是一个类型还是一个变量,因此需要添加typename关键字来明确说明这个表达式是一个类型。
但有些地方,实际上是只能出现类型的,但按照以前的C++标准,还是要求明确添加typename关键字来说明这个表达式是一个类型,这样造成开发模板时要写太多的typename。因此在C++20中,对这些地方进行放宽,明确只能出现类型的地方,可以不写typename。
另外,对于本地定义的类型,也明确不需要加typename。
具体例子如下:
// C++20 新特性(24):模板访问权限和typename的放宽
#include
#include
using std::cout, std::endl;
struct CA
{
typedef std::string TYPE;
};
template
/* 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();
return 0;
}