【C++】模板中template、typename的非常见用法

模板是指:C++程序设计中采用类型作为参数的程序设计,用于支持通用程序设计。C++的标准库提供许多有用的函数大多结合了模板的观念,如STL、IO Stream等。在模型的声明中,最最最常见的用法就是:

template <typename T>

值得注意的是:模板在编译的时候并不知道这个T是什么类型,只有在实例化的时候才能够知晓

因此,这会导致在模板较复杂的时候,可能会产生很多意想不到的理解上的歧义。为了规避这种歧义性,typename和template在某些特定的情况下,需要点明。这就是它们的非常见用法。

typename

先来看第一个歧义性的用法:

template <typename T>
class MyClass {
  T::SubType *ptr;
};

这里使用了类作用域操作符::,形如MyClass::name的用法通常存在三种:静态数据成员、静态成员函数和嵌套类型。例如:

class MyClass {
  static int A;       // 静态数据成员
  static int B();     // 静态成员函数
  typedef int C;      // 嵌套类型
}

那么请问,上面的例子中T::SubType指的哪一种?

  • 嵌套类型:定义了T类型下的SubType嵌套类型的指针ptr
  • 静态数据成员:T类型下的静态数据成员SubType,和ptr做乘法操作

如果是第一种含义,没有问题。如果是第二种含义,那么就出问题了:ptr未定义啊,此时编译器就会报错。当然,如果ptr是一个全局变量,那么就完全没有问题。它表示的是计算两数相乘的表达式,返回值被抛弃。

同一行代码能以两种完全不同的方式进行解释,而且在模板实例化之前,完全没有办法来区分它们,这种歧义性绝对是滋生各种bug的温床。

因此,在一些编译环境下,会直接报错:

main.cpp:3:3: error: missing 'typename' prior to dependent type name 'T::SubType'
  T::SubType *ptr;
  ^~~~~~~~~~
  typename 
1 error generated.

在C++标准中规定:对于用于模板定义的依赖于模板参数的名称,只有在实例化的参数中存在这个类型名,或者这个名称前使用了typename关键字来修饰,编译器才会将该名称当成是类型。除了以上这两种情况,绝不会被当成是类型。或者可以简单理解成:除了使用typename修饰之外,template内的任何标识符号都被视为一个值/成员而不是一个类型。可以参考文档:typename MSDN

也就是说,按照C++标准上说,T::SubType只能解释为第二种含义。如果要想指定乘第一种含义,即告诉编译器SubType是嵌套类型而不是静态数据成员,只需用typename修饰:

template <typename T>
class MyClass {
  typename T::SubType *ptr;
};

此时,关键字typename被用来作为类型之前的标识符号

需要注意的是:对于不会引起歧义的情况,仍然需要在前面加typename。例如:

template <typename T>
class MyClass {
  typename T::SubType ptr;
};

此时,如果将T::SubType理解成静态数据成员的话,那就两个数据成员,好像没有什么语法是这样的。那是不是就只能理解成嵌套类型,不需要修饰typename了呢?不行,此处仍还需typename修饰。

再加深一些印象:

template<typename T, typename T2>
void f_tmpl () { 
  typename T::foo *x;         // 这里我们需要typename来告诉编译器T::foo是一个type
  typename T::foo y;          // 这里我们需要typename来告诉编译器T::foo是一个type
  T2::foo *z;                 // 这里编译器默认T2::foo不是一个type
  T t;                        // 这里我们不需要typename,编译器也知道T是一个type
  detail::apply a;            /* 这里也不需要typename,因为没有template argument
                                 一切信息已知,apply不属于dependent type
                                 所以编译器也知道这里的apply是一个type */
}

最后强调一点:typename可以由模板声明或定义中任何位置的任何类型使用。但基类列表中不允许使用它,除非作为模板基类的模板参数

例如:

template <class T>
class C1 : typename T::InnerType        // Error - typename not allowed
{};

template <class T>
class C2 : A<typename T::InnerType>     // typename OK
{};

template

再来看第二个歧义性的用法:

template <typename T>
void func(T f) {
  T::cast<int>();
}

那么请问,上面的例子中T::cast<int>()可以怎么理解?

  • 静态成员函数:T类型下的静态成员函数cast(为模板函数),实参是void
  • 静态数据成员:T类型下的静态数据成员cast,判断是否小于int

如果是第一种含义,没有问题。如果是第二种含义,那么就出问题了:一个cast的数据成员和int怎么做小于的判断啊。

同一行代码能以两种完全不同的方式进行解释,而且在模板实例化之前,完全没有办法来区分它们,这种歧义性绝对是滋生各种bug的温床。

因此,在一些编译环境下,会直接报错:

main.cpp:3:6: error: use 'template' keyword to treat 'cast' as a dependent template name
  T::cast<int>(f);
     ^
     template 
1 error generated.

在C++标准中规定:template内的任何标识符号如果没有被关键字特意修饰,都优先被视为一个值/成员(typename也是,如果没有被typename修饰,就不会被认为是类型)。

也就是说,应该告诉编译器cast是模板静态成员函数,后面的<>表示模板,只需用template修饰:

template <typename T>
void func(T f) {
  T::template cast<int>();
}

此时,关键字template被用来作为模板的标识符号

需要注意的是:template前面不一定是::,还可以是.或者->。例如:

template <typename T>
void func(T f) {
  f.template cast<int>();
  f->template cast<int>();
}

此时,cast函数就不是模板静态成员函数,而是模板普通成员函数了。当然,如果不是模板函数,而是模板类,也是可以的。

一些例子

简单总结一下:关键字typename用于点明类型,关键字template用于点明模板

STL库中iterator_traits(迭代器萃取器)判断是否为POD类型:

typedef typename __type_traits<T1>::is_POD_type is_POD;

Sophus库中求SE3类的Jacobian时的代码片段:

Vector3<Scalar> const upsilon = upsilon_omega.template head<3>();
Vector3<Scalar> const omega = upsilon_omega.template tail<3>();

ROS库代码:

typedef std::basic_string<char,
                          std::char_traits<char>,
                          typename ContainerAllocator::template rebind<char>::other
                         >
  _joint_name_type;

相关阅读

【C++】模板中template、typename的非常见用法_template <typename proc>-CSDN博客

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值