考虑以下C ++代码示例:
namespace n
{
struct A {};
}
struct B {};
void foo(int) {}
template<typename T>
void quux()
{
foo(T());
}
void foo(n::A) {}
void foo(B) {}
int main()
{
quux<n::A>(); // Error (but works if you comment out the foo(int) declaration)
quux<B>(); // Works
return 0;
}
如注释中所示,模板实例化quux<n::A>()
导致编译器错误(在GCC 4.6.3上):
foo.cpp: In function ‘void quux() [with T = n::A]’:
foo.cpp:22:16: instantiated from here
foo.cpp:13:5: error: cannot convert ‘n::A’ to ‘int’ for argument ‘1’ to ‘void foo(int)’
有人可以向我解释发生了什么事吗?我希望它与quux<B>()
的工作方式相同。当foo
被视为依赖时,它必须与某些事情有关。不幸的是我的C ++ foo还不够好。当foo(int)
声明不存在时,该示例编译正常,这对我来说也是令人惊讶的。
欢迎提供任何提示,解释和解决方法。
更新1:
我不希望(读取不能)在foo(n::A)
的定义之前移动quux
的声明(这将避免错误)。
更新2:
感谢大卫指出相关问题Template function call confused by function with wrong signature declared before template。 Johannes Schaub - litb接受的答案提出了一个包装类解决方案,在我的情况下也可以作为一种解决方法。但是,我并不是百分之百满意。
更新3:
我通过将foo(n::A)
的定义放在命名空间n
中解决了这个问题。感谢Jesse Good和bames53的有用答案,不仅指出了标准的相关部分,还提供了替代解决方案。感谢DavidRodríguez - 当我不理解所提出的解决方案以及所有其他贡献者时,他的解释是运气。
答案 0 :(得分:2)
我认为规则是14.6.4.2p1:
对于依赖于模板参数的函数调用, 使用通常的查找规则找到候选函数(3.4.1, 3.4.2,3.4.3)除了:
- 对于使用非限定名称查找(3.4.1)或限定名称查找(3.4.3)的查找部分,仅 找到模板定义上下文中的函数声明。
- 仅针对使用关联命名空间(3.4.2)的查找部分 在模板定义上下文中找到的函数声明 或找到模板实例化上下文。
void foo(n::A) {}
在模板定义上下文中不可见,因为它来自foo
与n::A
不在同一名称空间中。所以它需要在模板定义之前可见,或者包含在同一名称空间中,如下所示:
namespace n
{
void foo(n::A) {}
}
答案 1 :(得分:1)
我的编译器给出的错误是:
main.cpp:11:5: error: call to function 'foo' that is neither visible in the template definition nor found by argument-dependent lookup
foo(T());
^
main.cpp:18:5: note: in instantiation of function template specialization 'quux<n::A>' requested here
quux<n::A>(); // Error (but works if you comment out the foo(int) declaration)
^
main.cpp:14:6: note: 'foo' should be declared prior to the call site or in namespace 'n'
void foo(n::A) {}
^
这清楚地表明了问题所在。
评论void foo(int)
不会使其正常工作;这只是编译器中的错误/扩展。
您提到您无法在void foo(n::A)
之前定义quux()
,但是当您在评论中说不能在命名空间n
中定义它时,您提供的原因就是似乎适用。如果没有您提到的其他问题,这应该可以解决问题。
template<typename T>
void quux()
{
foo(T());
}
namespace n {
void foo(n::A) {}
}
using n::foo; // in case there's any other code that depends on getting foo(n::A) from the global namespace
void foo(B) {} // this stays in the global namespace
如果你不能将void foo(n::A)
的定义移动到适当的两阶段查找的位置(再次,在quux()
之前或在命名空间{{1}之前有一种可能对你有用的hacky解决方案:转发声明 n
内foo()
的正确重载。
quux()
函数最终必须在与template<typename T>
void quux()
{
void foo(T);
foo(T());
}
相同的名称空间内定义,并且必须匹配“通用”前向声明。
或者还有另一种选择。大多数C ++编译器开始提供正确的两阶段名称查找只是相当近期,所以有很多代码不正确,但编译器想要支持。如果您无法更改代码,那么它可能是启用兼容性编译器选项的良好候选者;我的编译器使用标志quux()
来禁用两阶段名称查找,而是始终在实例化上下文中查找名称。