为什么我不能使用显式模板参数调用模板友元函数?
Why can't I call a template friend function with explicit template arguments?
考虑以下示例:
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
?
它是模板这一事实并不重要。在声明位置定义的友元函数只能通过 ADL 查找找到。当您使用模板参数时,编译器会尝试使用正常的非限定查找来查找名为 foo
的函数模板,但失败了。 foo(s)
使用 s
的关联命名空间(全局命名空间)查找 foo
并找到您定义的友元函数。
Clang at Godbolt 能够阐明一些问题:
<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 才成为语言的一部分,当时 P084R0 到达并修复了它。
由于 foo()
完全在 S
结构中实现,因此 main()
中看不到模板 foo
。因此,编译器不理解它需要做 ADL 并且无法找到 foo()
。就像@0x499602D2 说的。
一个解决方案是更新编译器,另一个解决方案是在 S
之外实现 foo
,并且可以选择添加前向声明以提供默认模板参数:
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)
,因为 ,并且您无法将其添加到 [=21] =] 因为它是 (b)
的重新声明,因此不能引入默认模板参数。
friend
class 主体中的函数定义不会使 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)
有效。这是因为依赖于参数的查找。在没有嵌套名称说明符的函数调用中,还会搜索 classes 和调用参数类型(以及其他)的封闭名称空间以查找匹配的函数重载。对于依赖于参数的查找,仅在 class 主体中声明的 friend
是可见的。通过这种方式,找到了调用 foo(s)
的匹配函数。
foo<void>(s)
应该以相同的方式工作,因为名称是不合格的并且 s
是 S
类型,所以 ADL 应该再次找到朋友 foo
在 S
.
但是,还有一个问题需要考虑。当编译器读取 foo
时,它必须决定 foo
是否可以是模板,因为它改变了 foo
之后 <
的解析。
为了决定这一点,在 foo
上进行了非限定名称查找。在 C++20 之前,仅当此查找找到具有该名称的某种模板时,foo
才会被视为模板名称。但是不合格的名称查找在您的情况下找不到任何东西,因为只有 foo
对于普通的不合格名称查找是不可见的。因此 foo
不会被视为模板,并且 foo<void>
不会被解析为模板 ID。
在 C++20 中,规则已更改,如果非限定名称查找找到具有该名称的普通函数或什么都没有,则 foo<void>
将也被视为模板 ID。在这种情况下,调用的以下 ADL 将找到 foo
并且调用将成功。
所以代码将像在 C++20 和 pre-C++20 中一样工作你实际上只需要通过名称 foo
声明 any 模板让 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)
}
考虑以下示例:
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
?
它是模板这一事实并不重要。在声明位置定义的友元函数只能通过 ADL 查找找到。当您使用模板参数时,编译器会尝试使用正常的非限定查找来查找名为 foo
的函数模板,但失败了。 foo(s)
使用 s
的关联命名空间(全局命名空间)查找 foo
并找到您定义的友元函数。
Clang at Godbolt 能够阐明一些问题:
<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 才成为语言的一部分,当时 P084R0 到达并修复了它。
由于 foo()
完全在 S
结构中实现,因此 main()
中看不到模板 foo
。因此,编译器不理解它需要做 ADL 并且无法找到 foo()
。就像@0x499602D2 说的。
一个解决方案是更新编译器,另一个解决方案是在 S
之外实现 foo
,并且可以选择添加前向声明以提供默认模板参数:
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)
,因为 (b)
的重新声明,因此不能引入默认模板参数。
friend
class 主体中的函数定义不会使 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)
有效。这是因为依赖于参数的查找。在没有嵌套名称说明符的函数调用中,还会搜索 classes 和调用参数类型(以及其他)的封闭名称空间以查找匹配的函数重载。对于依赖于参数的查找,仅在 class 主体中声明的 friend
是可见的。通过这种方式,找到了调用 foo(s)
的匹配函数。
foo<void>(s)
应该以相同的方式工作,因为名称是不合格的并且 s
是 S
类型,所以 ADL 应该再次找到朋友 foo
在 S
.
但是,还有一个问题需要考虑。当编译器读取 foo
时,它必须决定 foo
是否可以是模板,因为它改变了 foo
之后 <
的解析。
为了决定这一点,在 foo
上进行了非限定名称查找。在 C++20 之前,仅当此查找找到具有该名称的某种模板时,foo
才会被视为模板名称。但是不合格的名称查找在您的情况下找不到任何东西,因为只有 foo
对于普通的不合格名称查找是不可见的。因此 foo
不会被视为模板,并且 foo<void>
不会被解析为模板 ID。
在 C++20 中,规则已更改,如果非限定名称查找找到具有该名称的普通函数或什么都没有,则 foo<void>
将也被视为模板 ID。在这种情况下,调用的以下 ADL 将找到 foo
并且调用将成功。
所以代码将像在 C++20 和 pre-C++20 中一样工作你实际上只需要通过名称 foo
声明 any 模板让 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)
}