什么规范文本规定在-class 中定义的朋友只能通过非 ADL 查找找到(重新)在封闭的命名空间中声明一次?

What normative text governs that a friend defined in-class can be found by non-ADL lookup only once (re-)declared in the enclosing namespace?

以下所有标准参考均参考 N4659: March 2017 post-Kona working draft/C++17 DIS


声明为友元的函数也可以在友元声明处定义如下:

#include <iostream>

namespace a {
struct A {
    // Definition of 'a::foo()' 
    friend void foo() { std::cout << __PRETTY_FUNCTION__; }   
};
}  // namespace a

a::foo()这样的功能,有时被称为“隐藏的朋友”,无法通过不合格或合格的查找找到:

int main() {
    a::foo();  // error: no member named 'foo' in namespace 'a'
}

然而,根据 [basic.lookup.argdep]/4 它可以通过 ADL 找到 [emphasis 我的]:

When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier ([namespace.qual]) except that:

  • [...]
  • Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup ([class.friend]).
  • [...]

具体基于已定义的 class 类型的 ADL:

#include <iostream>

namespace a {
struct A {
    friend void foo(const A&) { std::cout << __PRETTY_FUNCTION__; }   
};
}  // namespace a

int main() {
    foo(a::A{});  // void a::foo(const a::A &)
}

但是,如果我们(重新)在定义它的 class 的命名空间范围内声明该函数,则可以通过 qualified/unqualified 查找找到它:

#include <iostream>

namespace a {
struct A {
    friend void foo() { std::cout << __PRETTY_FUNCTION__; }   
};
void foo();
}  // namespace a

int main() {
    a::foo();  // void a::foo()
}

问题

以下所有标准参考,除非另有明确说明,均参考N4659: March 2017 post-Kona working draft/C++17 DIS


朋友声明(定义与否)不会将新名称引入封闭的命名空间

[class.friend]/6 管理“隐藏朋友”的范围(仅在其朋友声明中声明和定义)[强调 我的]:

[class.friend]/6: A function can be defined in a friend declaration of a class if and only if the class is a non-local class ([class.local]), the function name is unqualified, and the function has namespace scope.

因此,即使函数的定义位于其友元声明中,友元函数也具有命名空间范围,并且可以通过封闭命名空间(class 在其中定义)。

#include <iostream>

namespace a {
struct A {
    // Definition of 'a::foo()' (note the namespace scope).
    friend void foo() { std::cout << __PRETTY_FUNCTION__; }   
};

// (re-)declaration of foo().
void foo();
}  // namespace 'a'

int main() {
    a::foo();  // void a::foo()
}

然而,要问的更普遍、更有趣的问题是

  • 友元声明而不是introduce/bind声明的名称命名空间作用域是什么?

[namespace.memdef]/3

管辖

[namespace.memdef]/3: If a friend declaration in a non-local class first declares a class, function, class template or function template the friend is a member of the innermost enclosing namespace. The friend declaration does not by itself make the name visible to unqualified lookup ([basic.lookup.unqual]) or qualified lookup ([basic.lookup.qual]). [ Note: The name of the friend will be visible in its namespace if a matching declaration is provided at namespace scope (either before or after the class definition granting friendship).  — end note ] [...]

因此,函数在友元声明中定义在这里并不相关,而是友元声明(即使不包含定义)不引入名称进入命名空间范围;在以下示例中,查找同样无法从朋友声明中找到名称:

#include <iostream>

namespace a {
struct A {
    friend void bar(); 
};
}  // namespace 'a'

int main() {
    a::bar();  // error: no member named 'bar' in namespace 'a'
}

我们可能还会注意到 C++20 草案,N4861 (March 2020 post-Prague working draft/C++20 DIS) has added a paragraph, albeit non-normative, that explicitly mentions this; from [basic.scope.pdecl]/13[强调我的]:

[ Note: Friend declarations refer to functions or classes that are members of the nearest enclosing namespace, but they do not introduce new names into that namespace ([namespace.memdef]). [...]