C++20 新特性(24):模板访问权限和typename的放宽

C++20 新特性(24):模板访问权限和typename的放宽

编码文章call10242025-02-10 11:18:2210A+A-

模板访问权限的放宽

当使用嵌套类(定义在类内部的类)进行模板特化或偏特化时,如果这个嵌套类的访问权限是私有或者保护时,按照以前的 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;
}
点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4