朋友声明的复杂范围规则有什么意义?

时间:2018-08-31 20:51:10

标签: c++ language-lawyer friend friend-function language-history

我最近发现朋友声明作用域遵循extremely peculiar rules-如果您有一个尚未声明的函数或类的friend声明(定义),它将在以下位置自动声明(定义)立即封闭的名称空间 but 对非限定和限定的查找是不可见的;但是,朋友 function 声明通过依赖于参数的查找仍然可见。

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
}

如果您查看标准(请参阅linked answer),它们将花费相当长的时间来实现这种古怪的行为,并在具有复杂规则的合格/不合格查询中添加了特定的例外。在我看来,最终结果使 1 极度令人困惑,同时又给实现添加了另一种情况。还是

  • 要求friend声明引用现有名称,句点;或
  • 允许他们声明现在的内容,但不更改普通名称查找(因此,这些名称就像在封闭的命名空间中“正常声明”一样可见)

似乎更易于实现,指定以及最重要的是要理解,我想知道:为什么他们会为这个麻烦而烦恼?他们试图涵盖哪些用例?在这些更简单的规则(尤其是第二条规则,与现有行为最相似)中,有什么会打破?


  1. 例如,在这种情况下

    struct M {
       friend class N;
    };
    N *foo;
    typedef int N;
    

    您得到comically schizophrenic error messages

    <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这样的东西,但是当您尝试提供有冲突的声明时,立即停止播放哑音。

1 个答案:

答案 0 :(得分:13)

为回答这个问题,您必须查看C ++的另一个主要功能:模板。

考虑这样的模板:

template <class T>
struct magic {
    friend bool do_magic(T*) { return true; }
};

在这样的代码中使用:

bool do_magic(void*) { return false; }

int main() {
    return do_magic((int*)0);
}

退出代码是0还是1

好吧,这取决于是否可以在任何可观察到的地方用magic实例化int
至少如果普通的查找规则可以找到仅声明为内联的friend函数。
而且,您不能仅仅通过注入所有可能的方法来解决这个难题,因为模板可以专门化。

曾经是这种情况,但是被禁止为“太不可思议”和“太不明确”。

名称注入还存在其他问题,因为它的定义不尽如人意。有关更多信息,请参见N0777: An Alternative to Name Injection from Templates