什么规范文本规定在-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()
}
问题
- 什么规范文本规定在-class 中定义的朋友只能通过非 ADL 查找找到(重新)在封闭的命名空间中声明一次?
以下所有标准参考,除非另有明确说明,均参考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: 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]). [...]
以下所有标准参考均参考 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()
}
问题
- 什么规范文本规定在-class 中定义的朋友只能通过非 ADL 查找找到(重新)在封闭的命名空间中声明一次?
以下所有标准参考,除非另有明确说明,均参考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: 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]). [...]