使用限定类型实例化的模板内的函数的名称解析

时间:2012-11-09 22:06:05

标签: c++ templates namespaces overloading name-lookup

考虑以下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 - 当我不理解所提出的解决方案以及所有其他贡献者时,他的解释是运气。

2 个答案:

答案 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) {}在模板定义上下文中不可见,因为它来自foon::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解决方案:转发声明 nfoo() 的正确重载。

quux()

函数最终必须在与template<typename T> void quux() { void foo(T); foo(T()); } 相同的名称空间内定义,并且必须匹配“通用”前向声明。


或者还有另一种选择。大多数C ++编译器开始提供正确的两阶段名称查找只是相当近期,所以有很多代码不正确,但编译器想要支持。如果您无法更改代码,那么它可能是启用兼容性编译器选项的良好候选者;我的编译器使用标志quux()来禁用两阶段名称查找,而是始终在实例化上下文中查找名称。