typename的双重意义

Posted coolcpp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了typename的双重意义相关的知识,希望对你有一定的参考价值。

《Effective C++》 Note

下面有两种template声明:

template<class T> class Widget();
template<typename T> class Widget();

当我们声明template类型参数,class和typename没有什么不同。但是使用typename可以暗示参数并非一定得是个class类型

C++并不总是把class和typename视为等价,有时候你一定得使用typename。
如下这个例子:

#include <iostream>
#include <vector>

template<typename C>
void print2nd(const C& container)
{
    if (container.size() >= 2)
    {
        C::const_iterator iter(container.begin());  //取得第一元素的迭代器
        ++iter;                //迭代器移向第二元素
        int value = *iter;     //将第二元素复制到value
        std::cout << value;    //输出value
    }
}

int main()
{
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    print2nd(v);

    return 0;
}

这个模板函数,接受一个STL兼容容器为参数,容器内持有的对象可被赋值为int。这个函数仅仅打印传进来容器的第二元素值。在VS 2015上可成功编译并运行,别的环境不知。

iter的类型是C::const iterator,到底是什么取决于template参数C。template内出现的名称若相依于某个template参数,称为从属名称(dependent names),若从属名称在class内呈嵌套状,称其为嵌套从属名称(nested dependent name)。C::const_iterator便是个嵌套从属类型名称。

value是个local变量,其类型为int,int并不倚赖任何template参数的名称,这样的名称称为非从属名称(non-dependent names)

嵌套从属类型有可能导致解析(parsing)困难。假设令print2nd这样:

template<typename C>
void print2nd(const C& container)
{
    C::const_iterator* x;
    ....
}

看起来声明的x为一个local变量,是个指针,指向一个C::const_iterator。之所以这么认为,只是我们已经知道C::const_iterator是个类型。若C::const_iterator不是个类型呢?如果C有个static成员变量而碰巧被命名为const_iterator,或x正好是个global变量名称呢?

那么上面的代码就不再是声明一个local变量,而是一个相乘操作:C::const_iterator * x。这是完全有可能的!

在知道C是什么之前,没有任何办法可以知道C::const_iterator是否为一个类型。C++有个规则可以解析这一歧义状态:如果解析器在template中遭遇一个嵌套从属名称,它便假设这名称不是个类型,除非你告诉它。所以缺省情况下嵌套从属名称不是类型。

所以上面的例子不是有效的C++代码,虽然它在这个机器上编译运行都无问题。iter声明式只有在C::const_iterator是个类型时才合理,但C++并不知道它是个合理的类型。我们需要告诉它,只需在紧临它之前放置关键字typename:

template<typename C>
void print2nd(const C& container)
{
    if(container.size() >= 2)
    {
        typename C::const_iterator iter(container.begin());
        .....
    }
}

规则为:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧临它的前一位置放上关键字typename。

typename只被用来验明嵌套从属类型名称:其他名称不该有它的存在。

template<typename C>           //允许使用"typename"或"class"
void f(const C& container      //不允许使用"typename"
       typename C::iterator iter);     //一定要使用"typename"

上述规则的例外是,typename不可以出现在base classes list内的嵌套从属名称之前,也不可在member initialization list中作为base class修饰符。如:

template<typename T>
class Derived: public Base<T>::Nested{   //base class list中不允许使用"typename"
public:
    explicit Derived(int x) : Base<T>::Nested(x)  //mem.init.list中不允许使用"typename"
    {
        //嵌套从属类型,既不在base class list中也不在mem.init.list中
        //作为一个base class修饰符需加上typename
        typename Base<T>::Nested temp;
    }
}

假设我们在写一个function template,它接受一个迭代器,而我们打算为该迭代器的对象做一份local复件temp。可以这样写:

template<typename, IterT>
void workWithIterator(IterT iter)
{
    typename std::iterator_traits<IterT>::value_type temp(*iter);
    ....
}
由于std::iterator_traits<IterT>::value_type是个嵌套从属类型名称,
value_type被嵌套于iterator_traits<IterT>之内而IterT是个template参数,所以我们必须将其前放置typename。

以上是关于typename的双重意义的主要内容,如果未能解决你的问题,请参考以下文章

在第6731次释放指针后双重免费或损坏

第56课.函数模板的概念和意义

一个片段的活动有啥意义吗?

使用双重逻辑非(!!)运算符感到困惑[重复]

如何在OpenCV中使用双重类型的地图重新映射

片段如何处理触摸?