22

考虑一下我在 IBM 网站上找到的这个例子:

#include <iostream>
using namespace std;

void f(double) { cout << "Function f(double)" << endl; }

template<class T> void g(T a) {
  f(123);
  h(a);
}

void f(int) { cout << "Function f(int)" << endl; }
void h(double) { cout << "Function h(double)" << endl; }

void i() {
  extern void h(int);
  g<int>(234);
}

void h(int) { cout << "Function h(int)" << endl; }

int main(void) {
    i();
}

它会打印什么?

  • 我改编此示例的 IBM 文档(可在此处获得)说它将打印:

    Function f(double)
    Function h(double)
    

    这样做的理由是依赖于模板参数的名称查找是在 的实例化之前执行的i(),所以它找到h(double)但没有找到h(int)

  • 当我使用 GCC 4.4.1 编译它时,它会打印:

    Function f(double)
    Function h(int)
    

    GCC 似乎在编译其他所有内容后都在模板中查找与模板参数相关的名称,因此它同时找到h(double)and h(int),并且更喜欢后者。

  • 当我使用 Clang 2.8 编译它时,它无法编译。编译器错误是:

    ibm_example.cc:8:3: error: use of undeclared identifier 'h'
      h(a);
      ^
    ibm_example.cc:16:3: note: in instantiation of function template specialization 'g<int>' requested here
      g<int>(234);
      ^
    1 error generated.
    

    Clang 似乎在声明模板的位置查找模板中依赖于模板参数的名称,因此它既没有找到也h(double)没有找到h(int)

哪一个是对的?

4

1 回答 1

13

他们都是正确的。不,真的,请继续阅读...

template<class T> void g(T a) {
  f(123);
  h(a);
}

这里,f是一个非从属名称,但h根据 14.6.2/1 是一个从属名称。f被查找

模板定义中使用的非依赖名称是使用通常的名称查找找到的,并在使用它们时绑定。

f立即向上查找并绑定到void f(double),此时唯一f可见的。

根据 14.6.4.1 的实例化点void g<int>(int)紧跟在 的定义之后void i(),在其中使用它。

[..]否则,这种特化的实例化点紧跟在引用特化的命名空间范围声明或定义之后。

这意味着解析依赖名称的源是在定义处可见的声明,template<class T> void g(T a)以及“来自与函数参数类型相关的命名空间的声明,既来自实例化上下文(14.6.4.1),也来自定义上下文”(14.6.4) .

但是,因为int是基本类型,所以关联命名空间的集合是空的(3.4.2)(不,甚至不包括全局命名空间),并且根据 14.6.4.2,它只是使用可以使用模板的关联命名空间进行查找实例化上下文,正常的非限定名称查找只能使用在模板定义上下文中可见的内容。这证实了 14.6.4 中所说的内容。

现在,奖励积分。14.6.4.2 继续说:

如果调用格式错误或找到更好的匹配,则在关联命名空间中的查找考虑所有翻译单元中这些命名空间中引入的具有外部链接的所有函数声明,而不仅仅是考虑在模板定义和模板中找到的那些声明实例化上下文,则程序具有未定义的行为。

该调用格式错误,因为查找失败(有关关联命名空间的部分不适用于此处),因此该行为是明确未定义的,因此任何事情都可能发生。因此,所看到的行为都没有显示不符合标准,尽管对我来说,Clang 的诊断似乎最符合标准。

(所有参考 ISO/IEC 14882:2011。)

于 2011-09-10T07:36:56.297 回答