【发布时间】:2015-03-06 17:14:32
【问题描述】:
小问题:运算符是否有特殊的模板查找规则,用于内部链接的重载解析,或者底部的代码是 GCC 中运算符的模板重载解析错误?
细节:我将带您了解我的推理,而不是粘贴一大段代码。让我们从一些简单的代码开始:
#include <iostream>
template<typename T> struct A{ T b; };
struct B{};
template<typename T>
void foo (const A<T>&a) { foo(a.b); }
void foo (const B&) { std::cout << "hello"; }
int main(){
A<B> b;
foo(b);
}
上面打印"hello",一切都很好。
现在让我们将foo 放在一个匿名命名空间中:
namespace {
template<typename T>
void foo (const A<T>&a) { foo(a.b); }
void foo (const B&) { std::cout << "hello"; }
}
代码现在无法编译。 Clang 说 error: call to function 'foo' that is neither visible in the template definition nor found by argument-dependent lookup 和 GCC template argument deduction/substitution failed。
这是因为foo(const B&) 是在foo<T> 之后定义的并且没有外部链接,如n4296 中所述:
[basic.link] 未命名命名空间或在未命名命名空间中直接或间接声明的命名空间具有 内部联动。所有其他命名空间都有外部链接。
[温度点] 依赖于模板参数的表达式的实例化上下文是一组声明 在同一翻译单元中模板特化的实例化点之前声明了外部链接。
[temp.dep.candidate] 对于后缀表达式是从属名称的函数调用, 使用通常的查找规则(3.4.1, 3.4.2)除了:
对于使用非限定名称查找 (3.4.1) 的查找部分,仅来自模板的函数声明 找到定义上下文。
对于使用查找的部分 关联的命名空间(3.4.2),只有函数声明在 模板定义上下文或模板实例化 找到上下文。
现在使用运算符也是一样的:
struct ostream {} cout;
template<typename T> struct A{ T t; };
struct B{};
namespace {
template<typename T>
ostream& operator<< (ostream& out, const A<T>&v)
{ return out << v.t; }
ostream& operator<< (ostream& out, const B&)
{ return out; }
}
int main(){
A<B> a;
cout << a;
}
GCC (4.7/4.8/4.9) 现在对代码非常满意,并使用 -Wall -Wextra -pedantic -ansi 给出零警告,而 clang 抱怨 'operator<<' 的方式与对 foo 的方式相同。
我没有发现标准中的运算符重载查找有任何例外,所以我认为这是 GCC 中的一个错误(功能?),但模板解析规则并不容易,所以我想我可以在提交错误之前检查这里。
你可以在here看到这个代码。
【问题讨论】:
-
感谢@T.C. 指出这一点。 GCC 中的运算符查找确实有些棘手。我仍然会将它作为一个单独的错误提交,因为上下文似乎已经足够不同,但根本原因可能是相同的。
-
我通过切换定义的顺序得到了
foo代码在 GCC 上编译。这符合你的预期吗?这是my fork of your live example。 -
@AustinMullins :是的,确实如此。这里的重点是
foo的示例根据标准格式错误,operator<<也应该如此,但它不是(对于 gcc)。您对代码的修复是正确的,如果您尝试编译上面的示例,clang 甚至会告诉您该怎么做:'foo' should be declared prior to the call site or in the global namespace
标签: c++ templates gcc language-lawyer argument-dependent-lookup