我最近发现友元声明作用域遵循极其特殊的规则——如果你有一个friend
尚未声明的函数或类的声明(定义),它会在紧接的命名空间中自动声明(定义),但它是不可见的不合格和合格的查找;但是,友元函数声明通过依赖于参数的查找仍然可见。
struct M {
friend void foo();
friend void bar(M);
};
void baz() {
foo(); // error, unqualified lookup cannot find it
::foo(); // error, qualified lookup cannot find it
bar(M()); // ok, thanks to ADL magic
}
如果您查看标准(请参阅链接的答案),他们会竭尽全力启用这种古怪的行为,在具有复杂规则的合格/不合格查找中添加特定例外。最终结果在我看来非常令人困惑1,还有另一个角落案例要添加到实现中。作为任一
- 要求
friend
声明提及现有名称、句号;或者 - 允许他们像现在一样声明东西,但不改变普通名称查找(因此,这些名称变得可见,就好像在封闭的命名空间中“正常”声明一样)
似乎更容易实现,指定,最重要的是,理解,我想知道:他们为什么要为这个烂摊子烦恼?他们试图涵盖哪些用例?在这些更简单的规则(特别是与现有行为最相似的第二条规则)下,什么会打破?
例如,在这种特殊情况下
struct M { friend class N; }; N *foo; typedef int N;
你收到可笑的精神分裂错误信息
<source>:4:1: error: 'N' does not name a type N *foo; ^ <source>:5:13: error: conflicting declaration 'typedef int N' typedef int N; ^ <source>:2:17: note: previous declaration as 'class N' friend class N; ^
编译器首先声称没有这样的东西
N
,但是当您尝试提供冲突的声明时立即停止装傻。