7

考虑这段代码:

#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) { ... }
4

3 回答 3

4

观察到的行为是正确的,与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()
}
于 2012-09-21T13:06:28.233 回答
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 中得到了修复。

于 2012-09-21T13:19:27.893 回答
2

当你调用do_stuff的类型astd::vector<int>. 编译器然后搜索 a并foo发现std::vector<int>它可以使用foo(A&)('错误' foofoo(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模板实例化之前出现的第二个的调用。

于 2012-09-21T12:14:41.253 回答