【问题标题】:What is the point of the complicated scoping rules for friend declarations?朋友声明的复杂范围规则有什么意义?
【发布时间】:2019-02-06 21:35:18
【问题描述】:

我最近发现友元声明作用域遵循extremely peculiar rules - 如果您有一个尚未声明的函数或类的friend 声明(定义),它会在紧接的命名空间中自动声明(定义) ,但是对非限定和限定查找是不可见的;但是,朋友 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 这样的东西,但是当您尝试提供冲突的声明时立即停止装傻。

【问题讨论】:

  • 我不太确定您在这里寻找什么。就像...为什么只对 ADL 可见?
  • @Barry:我在问他们为什么要为这个烂摊子而烦恼,例如只是让它们对所有查找可见,没有创建那些“幻影”名称的奇怪规则。标准中少了几段,少了几千个神经元浪费在记住特殊情况,更简单的编译器实现。我的(可能是错误的)隐含假设是,如果他们达到这个长度来创建这些奇怪的半可见实体,那么他们的目标是一些特定的用例,但我想不出任何用例。
  • @MatteoItalia 我刚刚发现1996 (!) paper 有建议的措辞来禁止这样做。现在我只需要找到理由......
  • @NirFriedman 确实是相关的;阅读答案中链接的论文以获取血腥细节。

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


【解决方案1】:

嗯,要回答这个问题,你必须看看 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-functions 仅声明为 inline 会被普通的查找规则找到。
而且你不能仅仅通过注入所有可能的东西来解决这个难题,因为模板可以被专门化。

曾有一段时间如此,但被认为“太神奇”和“定义太模糊”而被取缔。

名称注入还存在其他问题,因为它的定义不如预期的那么好。请参阅N0777: An Alternative to Name Injection from Templates 了解更多信息。

【讨论】:

  • 为什么要依赖magic的实例化?为什么该类定义不只是将函数模板注入封闭的命名空间?
  • @Brian 为什么会是函数模板?
  • 哇,这确实是一个令人信服的解释;老实说,恕我直言,“干净”的事情只是完全禁止名称注入(即我的问题中的第一个替代提案),但我怀疑它早于模板,当他们发现这个问题时,他们无法破坏兼容性?
  • ... 啊不,阅读论文注入的用例很清楚,实际上正是模板所需要的。直到!
  • @MatteoItalia 好吧,问题是它会禁用任何具有默认值的模板参数的类型推导。 template &lt;class T, class Alloc = std::allocator&lt;std::decay_t&lt;T&gt;&gt; auto make_thing(T&amp;&amp; t, Alloc a = Alloc()); 之类的东西将远不如以前有用。至少如果我理解正确的话。
猜你喜欢
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 2012-03-25
  • 2010-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多