如果原型是本地的,则使用流 I/O C++类型约束将失败

C++ type constraint using stream I/O fails if prototype is local

本文关键字:C++ 类型 失败 约束 原型 如果      更新时间:2023-10-16

在下面的MCVE中,C++可以毫不费力地识别int可以使用流I/O读取或打印。但它认为A不可能,尽管有<<>>的原型。

#include <iostream>
using namespace std;
template<typename T>
concept Readable = requires(std::istream& in, T& t)
{
in >> t;
};
template<typename T>
concept Printable = requires(std::ostream& out, T t)
{
out << t;
};
int main()
{
class A {};
ostream& operator<< (ostream&, const A&);
istream& operator>> (istream&, A&);
std::cout << Readable<int> << Printable<int> << 'n';
std::cout << Readable<A>   << Printable<A>   << 'n';
return 0;
}

输出为

11
00

如果我使原型和类全局化,则问题已解决(它打印11,而不是00,用于输出的第二行(。

显然,我会正常地使原型全局化。但是我不明白范围规则如何让约束知道它的定义位置,只要它被定义。 当原型是本地时,为什么它不起作用?

在两个表达式中 -in >> tout << t-t是一个依赖名称,这意味着operator>>operator<<的名称查找过程被推迟到已知T(例如,Readable<A>Printable<A>的第一次出现,其中T=A(。

此时,编译器可以执行非 ADL 查找(从模板定义的上下文中(和 ADL 查找。

对于非 ADL 查找,它找不到operator>>和概念定义之前的operator<<匹配声明。

对于 ADL 查找,它会考虑关联的命名空间,特别是A的命名空间。然而,类A是在函数main()中局部定义的,为此:

[class.local]/p1:

类可以在函数定义中声明;这样的类称为局部类。本地类的名称是其封闭范围的本地名称。本地类在封闭作用域的范围内,[...]

虽然名称Amain()的本地名称,但类本身存在于包含main()的命名空间中 - 全局范围。

这意味着在 ADL 中,编译器可以在A范围内查找运算符,也可以在全局范围内查找运算符。但是,对于这两个运算符声明,以下规则适用:

[basic.link]/p7:

当找不到具有链接的实体的块范围声明引用其他某个声明时,该实体是最内层封闭命名空间的成员。但是,此类声明不会在其命名空间范围内引入成员名称。

默认情况下,函数声明具有外部链接。因此,这两个运算符对于名称查找都不可见,并且不满足概念。