模板函数查找

时间:2012-09-21 11:59:13

标签: c++ templates

考虑这段代码:

#include <iostream>
#include <vector>

template<typename A>
void foo(A& a) {
    std::cout << "the wrong foo" << std::endl;
}

template<typename A>
void do_stuff(A& a) {
    foo(a);
}

template<typename X>
void foo(std::vector<X>& a) {
    std::cout << "the right foo" << std::endl;
}

int main()
{
    std::vector<int> q;
    do_stuff(q);
}

为什么称它为“错误的”foo?如果删除foo的第一个声明,则调用右foo。

我正在使用gcc 4.6.3。

更新 如果按以下顺序声明函数,则调用右foo。

template<typename A> void do_stuff(A& a) { ... }
template<typename A> void foo(A& a) { ... }
template<typename X> void foo(std::vector<X>& a) { ... }

3 个答案:

答案 0 :(得分:3)

观察到的行为是正确的,因为foo(a)是依赖于类型的表达式:

14.6.2.2 Type-dependent expressions                         [temp.dep.expr]

1) Except as described below, an expression is type-dependent if any
   subexpression is type-dependent.

2) this is type-dependent if the class type of the enclosing member
   function is dependent (14.6.2.1).

3) An id-expression is type-dependent if it contains

    — an identifier associated by name lookup with one or more declarations 
      declared with a dependent type,
    ...

且在14.6.4(依赖名称解除)下:

14.6.4.2 Candidate functions                              [temp.dep.candidate]

For a function call that depends on a template parameter, the candidate
functions are found using the usual lookup rules (3.4.1, 3.4.2, 3.4.3) except
that:

— For the part of the lookup using unqualified name lookup (3.4.1) or qualified
  name lookup (3.4.3), only function declarations from the template definition 
  context are found.
— For the part of the lookup using associated namespaces (3.4.2), only function
  declarations found in either the template definition context or the template
  instantiation context are found.

If the function name is an unqualified-id and the call would be ill-formed or
would find a better match had the lookup within the associated namespaces
considered all the function declarations with external linkage introduced in
those namespaces in all translation units, not just considering those
declarations found in the template definition and template instantiation
contexts, then the program has undefined behavior.

选择了“错误”foo(),因为这是模板定义点中唯一可见的,而“右”foo()未被考虑,因为它不是在与函数参数类型相关联的命名空间中。

如果您修改代码以使“右”foo()位于关联的命名空间中,则会选择它而不是“错误的”foo()。 (在这种特殊情况下,标准不允许这样做,所以不要执行下面的操作,但是使用自己的命名空间/类型,这是它应该如何工作)

#include <iostream>
#include <vector>

template<typename A> void foo(A& a)
{
    std::cout << "the wrong foo" << std::endl;
}

template<typename A>
void do_stuff(A& a) {
    foo(a);
}

namespace std { // evil, don't do this with namespace std!

template<typename X>
void foo(std::vector<X>& a) {
    std::cout << "the right foo" << std::endl;
}

}

int main()
{
    std::vector<int> q;
    do_stuff(q); // calls the "right" foo()
}

答案 1 :(得分:3)

在模板定义中,在定义模板的位置执行非依赖名称(即,不依赖于模板参数的foo之类的名称查找),而不是在实例化的位置。这由标准规定:

  

C ++ 11 14.6.3模板定义中使用的非依赖名称可以使用通常的名称查找找到,并在使用它们时绑定。

并通过与您类似的示例进行说明:

void g(double);
void h();

template<class T> class Z {
public:
  void f() {
    g(1);           // calls g(double)
    h++;            // ill-formed: cannot increment function;
                    // this could be diagnosed either here or
                    // at the point of instantiation
  }
};

void g(int);        // not in scope at the point of the template
                    // definition, not considered for the call g(1)

关于您的更新:我相信,foo之后放置do_stuff个声明,程序应该是格式错误的,GCC不正确(显然)推迟查找,直到实例化为止当它在使用点失败时。

更新:如评论中所述,此行为确实不正确,并已在gcc-4.7中修复。

答案 2 :(得分:2)

当您致电do_stuff时,a的类型为std::vector<int>。然后,编译器会搜索foo std::vector<int>,并发现它可以使用foo(A&)('错误'foo),foo(std::vector<B>&)尚未使用声明。如果删除此声明,则应该收到编译器错误,因为必须先定义模板才能使用它们。如果不这样做,可能是gcc模板系统中的一个错误而不是语言的一个特性。尝试使用clang++,您应该看到:

test.cpp:6:5: error: call to function 'foo' that is neither visible in the template definition nor found by
  argument-dependent lookup
foo(a);
^

编辑:这似乎是clang++处理模板的方式中的错误,它应该能够解析对模板实例化之前出现的第二个foo的调用。