GCC、Clang和IBM在执行模板参数相关名称查找时存在分歧。哪个是正确的?

23

考虑一下我在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();
}

它会打印什么?
  • The IBM documentation I adapted this example from, available here, says it will print:

    Function f(double)
    Function h(double)
    

    The rationale for this is that template-parameter-dependent name lookup is performed right before the instantiation of i(), so it finds h(double) but not h(int).

  • When I compile it using GCC 4.4.1, it prints:

    Function f(double)
    Function h(int)
    

    GCC seems to be looking up the template-parameter-dependent names in the template after everything else has been compiled, so it finds both h(double) and h(int), and prefers the latter.

  • When I compile it using Clang 2.8, it fails to compile. The compiler error is:

    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 seems to be looking up the template-parameter-dependent names in the template at the point where the template is declared, so it finds neither h(double) nor h(int).

哪一个是正确的?


可能是选择重载的模板函数的规则是什么?的重复问题。 - Foo Bah
对于GCC 4.5.1,没有任何更改。 - ergosys
1
@Foo:这绝对不是那个问题的重复——那一个是关于重载解析的,而这一个是关于范围和在定义点可见的符号。 - ildjarn
5
这是一个非常好的问题! - James McNellis
1个回答

14

它们都是正确的。真的,继续阅读...

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

在这里,f是一个非相关的名称,但是根据14.6.2/1,h是一个相关的名称。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。)

可以用不同的绑定来解释未定义行为的部分,这对我来说似乎更有意义:“如果在相关命名空间内考虑了所有翻译单元中引入的具有外部链接的函数声明,那么{{调用将是非法的}或{将找到更好的匹配}},...那么程序就会有未定义的行为”。 - Johannes Schaub - litb
或者换句话说,如果查找与关联命名空间中的声明一起考虑模板定义和模板实例化上下文中找到的声明之外的东西,那么(如果该调用不符合规范或找到更好的匹配)程序的行为是未定义的。 - Johannes Schaub - litb
1
在课程结束时,我只能评估哪种解释对我更有意义。当这些规则制定时,我不在场。如果标准清晰地列出了一个项目列表,那就更好了。现在,人们无法确定它是指“如果格式不正确...,行为未定义”还是指“如果格式不正确...,在相关命名空间中查找后,行为未定义”。顺便说一句,GCC4.7 trunk现在遵守这些规则,并像Clang一样运行。 - Johannes Schaub - litb
我对你问题中的这部分理解感到头疼:只有使用相关命名空间的查找才能使用模板实例化上下文,普通的未限定名称查找只能使用模板定义上下文中可见的内容。 - M3taSpl0it
所以我尝试在您指出的C++标准中查找它,结果写在那里:14.6.4.2 “对于使用未限定名称查找(3.4.1)的查找部分,仅可以找到来自模板定义上下文的具有外部链接的函数声明。” 我无法理解最后一句话“模板定义的外部关联”是什么意思? - M3taSpl0it
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接