请考虑以下示例:
struct S {
template<typename T = void>
friend void foo(S) {
}
};
int main() {
S s;
foo(s); // (1)
foo<void>(s); // (2)
}
我的GCC 9.2.0未能编译(2)
,错误如下:
a.cpp: In function 'int main()':
a.cpp:10:5: error: 'foo' was not declared in this scope
10 | foo<void>(s);
| ^~~
a.cpp:10:9: error: expected primary-expression before 'void'
10 | foo<void>(s);
| ^~~~
然而,(1)
工作得很好。为什么会这样呢?如何使用显式模板参数调用foo
?
发布于 2020-03-07 18:44:28
类体中的friend
函数定义不会使friend
函数在封闭的名称空间范围内显示到通常的非限定名称查找(尽管它们被放置在这个命名空间范围中)。
为了使其可见,您需要在命名空间范围中为模板添加一个声明(这在定义之前还是之后并不重要):
struct S {
template<typename T = void>
friend void foo(S) {
}
};
template<typename T>
void foo(S);
int main() {
S s;
foo(s); // (1)
foo<void>(s); // (2)
}
现在的问题是为什么foo(s)
工作。这是因为依赖于参数的查找。在没有嵌套名称说明符的函数调用中,还搜索调用参数类型(以及其他类型)的类和包围名称空间,以查找匹配的函数重载。对于依赖于参数的查找,只能在类体中声明的friend
s是可见的。这样就找到了调用foo(s)
的匹配函数。
foo<void>(s)
应该以同样的方式工作,因为名称是不合格的,而且s
是S
类型的,所以ADL应该再次在S
中找到朋友foo
。
然而,还有另一个问题要考虑。当编译器读取foo
时,它必须决定foo
是否可以是一个模板,因为它在foo
之后改变了对<
的解析。
要确定这一点,需要在foo
上进行不限定名称查找。在C++20之前,只有在查找找到某种类型的模板时,foo
才会被视为模板名。但是,在您的情况下,不限定名查找没有发现任何内容,因为唯一的foo
对于普通的非限定名查找是不可见的。因此,foo
将不被视为模板,foo<void>
也不会被解析为模板id。
在C++20中,规则被更改,如果不限定名称查找找到一个普通函数,或者完全不使用该名称,那么foo<void>
也将被视为一个模板id。在这种情况下,调用的下面的ADL将找到foo
,并且调用将成功。
因此,代码将像在C++20和pre++20中一样工作,实际上,您只需要声明任何名为foo
的模板就可以得到foo<void>(s)
,以找到由ADL建立的好友foo
。例如:
struct S {
template <typename T = void>
friend void foo(S) {}
};
template<int>
void foo();
int main() {
S s;
foo(s); // (1)
foo<void>(s); // (2)
}
发布于 2020-03-07 18:38:39
Clang 在哥德波特能够提供一些启示:
<source>:9:5: warning: use of function template name with no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions]
foo<void>(s);
^
1 warning generated.
在C++20到达并修复它之前,这种语法似乎并不是语言的一部分。
由于foo()
是在S
结构中完全实现的,因此在main()
中没有可见的模板foo
。因此,编译器不理解它需要做ADL,并且无法找到foo()
。比如@0x499602D2。
一种解决方案是更新编译器,另一种解决方案是在S
之外实现S
,还可以添加一个前向声明来提供默认的模板参数:
struct S;
template<typename T = void>
void foo(S); // (a)
struct S {
template<typename T>
friend void foo(S); // (b)
};
template<typename T>
void foo(S) { // (c)
// Needs S to be complete.
}
int main() {
S s;
foo(s);
foo<void>(s);
}
如果您试图省略前向声明(a)
,您将发现您无法向(b)
添加默认模板参数,因为是原因,而不能将其添加到(c)
,因为它是对(b)
的重新声明,因此不能引入默认的模板参数。
发布于 2020-03-07 18:28:43
它是一个模板的事实并不重要。只有通过ADL查找才能找到在声明它们的位置定义的朋友函数。当您使用模板argments时,编译器试图查找一个名为foo
的函数模板,该模板具有正常的不合格查找,但失败。foo(s)
使用相关的s
名称空间(全局名称空间)查找foo
,并找到您定义的朋友函数。
https://stackoverflow.com/questions/60580782
复制相似问题