名称解析使用限定类型实例化的模板内部的函数
考虑下面的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:
我不希望(读不能)在定义quux
之前移动foo(n::A)
的quux
(这会避免错误)。
更新2:
感谢大卫指出相关问题模板函数调用困惑的功能与模板之前声明的错误签名。 Johannes Schaub - litb接受的答案提出了一个包装类解决方案,在我的情况下,它也可以用作解决方法。 不过,我对此并不满意。
更新3:
我通过在命名空间n
放入foo(n::A)
的定义来解决这个问题。 感谢Jesse Good和bames53的有益答案,它不仅指出了标准的相关部分,还提供了替代解决方案。 感谢DavidRodríguez - 当我不理解所提出的解决方案以及所有其他贡献者时他的解释。
我认为规则是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) {}
在模板定义上下文中不可见 ,因为它在after和foo
与n::A
不在同一个名称空间中。 所以它需要在模板定义之前可见或包含在如下所示的相同名称空间中:
namespace n
{
void foo(n::A) {}
}
我的编译器给出的错误是:
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)
并不能使它工作。 这只是编译器中的一个错误/扩展。
你提到你不能在quux()
void foo(n::A)
之前定义void foo(n::A)
,但是当你在注释中说你不能在名字空间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()
之前或在命名空间n
内部),有一种hacky解决方案可用于你:向前声明在quux()
中foo()
的正确重载。
template<typename T>
void quux()
{
void foo(T);
foo(T());
}
该函数最终必须在与quux()
相同的名称空间内定义,并且必须与“通用”前向声明相匹配。
或者还有另一种选择。 大多数C ++编译器开始提供正确的两阶段名称查找是相当新近的,因此有很多代码是不正确的,但是编译器想要支持。 如果您不能更改代码,那么它可能是启用兼容性编译器选项的理想选择; 我的编译器采用标志-fdelayed-template-parsing
来禁用两阶段名称查找,而是总是在实例化上下文中查找名称。
上一篇: Name resolution of functions inside templates instantiated with qualified types