7

考虑以下 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:

感谢 David 指出相关问题Template function call 被在 template 之前声明的签名错误的函数混淆。Johannes Schaub 接受的答案 - litb 提出了一个包装类解决方案,在我的情况下也可以作为一种解决方法。但是,我对它并不是 100% 满意。

更新 3:

我通过将 in 的定义foo(n::A)放入 namespace解决了这个问题n。感谢 Jesse Good 和 bames53 的有用回答,他们不仅指出了标准的相关部分,还提供了替代解决方案。感谢 David Rodríguez - dribeas 在我没有正确理解所提出的解决方案时的解释以及所有其他贡献者。

4

2 回答 2

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) {}
}
于 2012-11-09T22:17:27.870 回答
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(),但是当您在评论中说您无法在 namespacen中定义它时,您给出的原因似乎并不适用。这应该可以解决问题而没有您提到的其他问题。

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)通过适当的两阶段查找(同样,在 namespace之前quux() n内部)将定义移动到它的工作位置,那么有一种可能对您有用的 hacky 解决方案:前向声明foo() inside quux()的正确重载。

template<typename T>
void quux()
{
    void foo(T);
    foo(T());
}

该函数最终必须在相同的命名空间中定义,quux()并且它必须与“通用”前向声明相匹配。


或者还有另一种选择。直到最近,大多数 C++ 编译器才开始提供正确的两阶段名称查找,因此有很多代码不正确,但编译器希望支持这些代码。如果您无法更改代码,那么它可能是启用兼容性编译器选项的好选择;我的编译器使用该标志-fdelayed-template-parsing来禁用两阶段名称查找,而是始终在实例化上下文中查找名称。

于 2012-11-12T15:35:33.847 回答