在模板中执行参数依赖名称查找时的奇怪行为

时间:2014-01-04 18:00:03

标签: c++ templates name-lookup

最近,我正在研究模板类中名称的众所周知的“两阶段名称查找”的确切含义。虽然我已经阅读了很多关于此的文章,但我仍然无法了解这一切。现在我对下面显示的代码感到困惑:

template<typename T>
class A
{
public:
    void f(T, T){};
};

namespace ns
{
    typedef int TT;
    void f(int, int){};
};

template<typename T>
class B : public A<T>
{
public:
    void g()
    {

        //f(T(), T()); // it's fine for error here
        typedef ns::TT TTT;
        f(TTT(), T()); // why this issued an error?
        f(ns::TT(), T()); // and this?
    }
};

/* I also think it's OK to move ns here */
// namespace ns
// {
//  typedef int TT;
//  void f(int, int){};
//};

int main()
{
    B<int> b;
    b.g();
}

请注意第二条评论。由于“f”是从属名称,因此应将其查找延迟到“main”函数中的实例化。此时,编译器应在main函数的范围内执行与参数相关的名称查找。我想现在它应该在命名空间ns中发现函数,但它仍然发出了编译错误:

1.cpp: In instantiation of 'void B<T>::g() [with T = int]':
1.cpp:30:6:   required from here
1.cpp:23:15: error: 'f' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]    f(TTT(), T()); //why this issued an error?
               ^
1.cpp:23:15: note: declarations in dependent base 'A<int>' are not found by unqualified lookup
1.cpp:23:15: note: use 'this->f' instead

有人可以向我解释一下吗?感谢。

1 个答案:

答案 0 :(得分:3)

依赖于参数的查找仅搜索参数类型的关联类和名称空间。 typedef只是一个透明的别名,类似于using声明。

从标准n3485草案中,[basic.lookup.argdep] / 2关于依赖于参数的查找:

  

对于函数调用中的每个参数类型T,都有一组零个或多个关联的命名空间以及一组零个或多个要考虑的关联类。命名空间和类的集合完全由函数参数的类型(以及任何模板模板参数的命名空间)决定。 用于指定类型的Typedef名称和using声明对此集合没有贡献。

[强调我的]


template<typename T>
class A
{
public:
    void f(T, T){};
};

namespace ns
{
    typedef int TT;
    void f(int, int){};
};

template<typename T>
class B : public A<T> // note the base class is dependent
{
public:
    void g()
    {
        //f(T(), T()); // it's fine for error here
        typedef ns::TT TTT;
        f(TTT(), T()); // why this issued an error?
        f(ns::TT(), T()); // and this?
    }
};

由于基类是相关的,因此在非限定查找期间不会搜索它。因此,可以使用参数依赖查找找到f。您正确地声明仅在“第二阶段”(在实例化时)搜索f,就像在您的调用中一样,至少有一个参数依赖于模板参数。

但是,ns 是一个从属名称,以及TT(在ns::TT中)。因此,必须先声明名称空间TT,然后才能在g的定义中使用它们。

是否编写f(ns::TT(), T())f(T(), T())不会影响在参数依赖查找期间搜索f的一般规则:只有关联的命名空间和参数类型的类( Tns::TT)。对于int,两者都是B<int>::g(),因此没有关联的类和名称空间。

f(TTT(), TTT())更改查找,因为f现在是在第一个名称查找阶段查找的(它不是从属名称)。


以下是与参数相关的查找示例:

namespace ns
{
    struct TT {};
    void f(TT, TT) {}
}

int main()
{
    ns::TT x;
    f(x, x);
}

现在,您也可以在类模板的成员函数中执行此操作:

namespace ns
{
    struct TT {};
    void f(TT, TT) {}
}

template<typename T>
struct B
{
    void g()
    {
        f(T(), T());
    }
};

int main()
{
    B<ns::TT> x;
    x.g();
}

但是,正如我所说,依赖于参数的名称查找不适用于int等基本类型。

在上面的示例中,f再次依赖,因为至少有一个参数依赖于模板参数。因此,你也可以写:

template<typename T>
struct B
{
    void g()
    {
        f(T(), T()); // looked up during the second phase,
                     // from the point of instantiation
    }
};

namespace ns
{
    struct TT {};
    void f(TT, TT) {}
}

int main()
{
    B<ns::TT> x;
    x.g();
}