在2012 ACCU C++ Pub quiz的问题15中,我对结果感到困惑。
#include <iostream>
template<typename T> void P(T x) { std::cout << x; }
void foo(char a) { // foo 1
P(3);
P(a);
}
template <typename... A> // foo 2
void foo(int a, A... args) {
foo(args...);
P(a);
}
template <typename... A>
void foo(char a, A... args) { // foo 3
P(a);
foo(args...);
}
int main()
{
foo('1','2',48,'4','5');
}
我推断它会调用foo 3
,foo 3
,foo 2
,foo 3
,foo 1
,从而输出1243548
。实际输出为12355248
,并在我的调试器中确认为foo 3
,foo 3
,foo 2
,foo 2
,foo 1
。我无法理解为什么第四次foo
来电foo 2
而不是foo 3
。
作为参考,我使用gcc 4.8.1 g++ -g -Wall -std=c++11 -Weffc++ -Wextra -O0 /tmp/foo.cpp -o /tmp/foo
进行编译,并且没有任何警告。
编辑:我刚刚在Visual Studio Express 2013上试过它,它提供了1243548
,也没有任何警告。
这是GCC / VS中的编译器错误,还是规范中那些尴尬的未指定行为部分之一?
答案 0 :(得分:3)
它看起来像是声明的顺序。如果你转发声明foo的相关重载超过foo 2那么你会看到你期望的结果,即把它放在foo 2之上:
template <typename... A>
void foo(char a, A... args);
标准的相关部分见于3.4.1.4:
在全局范围内使用的名称,在任何函数,类或之外 用户声明的命名空间,应在全局使用之前声明 范围。
并在14.6.4.1依赖名称解析:
在解析依赖名称时,来自以下来源的名称是 考虑:
- 在定义点可见的声明 模板。
- 来自与之关联的名称空间的声明 来自实例化上下文的函数参数的类型 (14.6.4.1)和定义背景。
由于args
是从属类型,因此名称解析仅将可见名称视为模板定义的点。此时尚未声明foo 3
,因此无法在重载决策中考虑此问题。在此基础上,Visual Studio在允许使用foo 3
时似乎是错误的。
答案 1 :(得分:1)
foo 2
无法致电foo 3
,因为foo 3
不在foo 2
的范围内。