Name lookup in class template

最近在项目中需要实现一些自定义容器及其迭代器,遇到的一点小问题,记录在此。

通常在写自定义迭代器类时,为了配合std::iterator_traits以方便使用各种std的泛型算法,通常会继承std::iterator。 std::iterator很简单,只是定义了一些类型的类模版,不包含任何成员变量及方法。

template <class Category, class T, class Distance = ptrdiff_t,
          class Pointer = T*, class Reference = T&>
struct iterator {
    typedef T         value_type;
    typedef Distance  difference_type;
    typedef Pointer   pointer;
    typedef Reference reference;
    typedef Category  iterator_category;
};

这里假设我们要定义的迭代器类叫做MyIter,容器类叫做MyContainer,不难写出以下类定义。

template<typename T>
class MyIter : public std::iterator<forward_iterator_tag, T>
{
    MyContainer<value_type> *_container;
    int _offset;
}

但是奇怪的是上述自定义的迭代器类MyIter在vs2008下可以正常编译运行,在gcc下却出错提示

'value_type' was not declared in this scope

看起来编译器在查找value_type这个类型名称时没找到,但我们明明在基类中有用typedef定义。那么很自然的就想到,是不是编译器在遇到value_type这个名称时没有去基类的scope里查找对应的名字。

经过一翻查找,在C++ standard 14.6.2.3中找到有如下说明:

In the definition of a class or class template, the scope of a dependent base class (14.6.2.1) is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.

这句话的意思是说,在类或类模版定义中,如果基类是dependent class。那么在派生类的类模版或者成员定义,以及实例化中,对于unqualified name,不会去基类的scope中查找。 dependent class的定义比较复杂。这里我们先简化的理解为基类依赖了模版参数T那么就是一个dependent class。详细的定义参考C++标准14.6.2.1。

回到我们这个例子中,我们的自定义迭代器类模版MyIter继承了一个依赖模版参数的基类std::iterator 因此,虽然std::iterator用typedef定义了一系列的类型,但是我们在派生类中使用一个unqualified name —— ‘value_type’,并不会去基类的scope查找。而在派生类和全局作用域中我们又没有定义这个类型,导致编译出错。 正确的用法是要显式的写出类型名所在的scope并且用上typename这个disambiguator。

template<typename T>
class MyIter : public std::iterator<forward_iterator_tag, T>
{
    MyContainer<typename std::iterator<forward_iterator_tag, T>::value_type> *_container;
    int _offset;
}

这样才可以编译通过。至于为什么不这么写在vs上也可以正常编译,我还没去找微软官方的资料。只是众所周知各家编译器都有着一些非标准的实现,可以认为是vs提供的一项便利吧。

另外,我们注意到标准中这条名字查找的规则,上述限制是针对基类是依赖模版参数的情况下成立的。如果我们的基类并不是类模版,那么基类中的typedef定义的类型是可以在派生类中直接用不需要显式的写出scope的。 例如

class Base
{
    typedef int value_type;
};
template<typename T>
class MyIter2 : public Base
{
    MyContainer<value_type> *_container;
    int _offset;
};

或者

template<typename T>
class MyIter : public std::iterator<forward_iterator_tag, int>
{
    MyContainer<value_type> *_container;
    int _offset;
}

这两种情况中,基类都不再依赖模版参数。于是使用基类的typedef过的类型时不在需要显式的指定scope了。

虽然不知道这么设计的目的是什么。但是根据C++标准的例子,

struct A {
struct B { / ... / };
    int a;
    int Y;
};
int a;
template<class T> struct Y : T {
struct B { / ... / };
    B b; // The B defined in Y
    void f(int i) { a = i; } // ::a
    Y* p; // Y<T>
};
Y<A> ya;

上述的代码中。虽然在类A中定义了B, a和Y。 但是在类模版Y的实例化中。我们可以成功的定义Y::B,Y*并且绑定全局的符号::a。当然,这么做有什么特别的意义没有我也不知道。

详细的关于dependent class以及相关概念可以参考这里

links