c++ - 为什么在没有附加声明的情况下不将友元函数视为声明它的类的命名空间的成员?
问题描述
假设我们有一个foo
来自命名空间的类,space
它声明了一个friend
名为 的函数bar
,该函数稍后会被定义,如下所示:
namespace space {
struct foo {
friend void bar(foo);
};
}
namespace space {
void bar(foo f) { std::cout << "friend from a namespace\n"; }
}
据我了解,friend void bar(foo);
声明 bar
是一个自由函数,里面space
有一个foo
按值。要使用它,我们可以简单地执行以下操作:
auto f = space::foo();
bar(f);
我的理解是我们不必说space::bar
,因为 ADL 会看到它bar
的定义与(其参数)相同namespace
,foo
并允许我们省略全名限定。尽管如此,我们被允许限定它:
auto f = space::foo();
space::bar(f);
哪个工作(并且应该工作)完全相同。
当我介绍其他文件时,事情开始变得奇怪。假设我们将类和声明移动到foo.hpp
:
#ifndef PROJECT_FOO_HPP
#define PROJECT_FOO_HPP
namespace space {
struct foo {
friend void bar(foo);
};
}
#endif //PROJECT_FOO_HPP
和定义foo.cpp
:
#include "foo.hpp"
#include <iostream>
namespace space {
void bar(foo f) { std::cout << "friend from a namespace\n"; }
}
请注意,我所做的只是将(没有更改任何代码)东西移到了.hpp
-.cpp
对。
那时发生了什么?好吧,假设我们#include "foo.hpp"
,我们仍然可以这样做:
auto f = space::foo();
bar(f);
但是,我们不再能够做到:
auto f = space::foo();
space::bar(f);
这没有说:error: 'bar' is not a member of 'space'
,这很令人困惑。我相当肯定那bar
是的成员space
,除非我严重误解了某些东西。同样有趣的是,如果我们另外声明 (again!) bar
,但在 之外,foo
它可以工作。我的意思是,如果我们改成foo.hpp
这样:
#ifndef PROJECT_FOO_HPP
#define PROJECT_FOO_HPP
namespace space {
struct foo {
friend void bar(foo);
};
void bar(foo); // the only change!
}
#endif //PROJECT_FOO_HPP
现在可以了。
头文件/实现文件是否有改变预期(至少对我而言)行为的东西?这是为什么?这是一个错误吗?我正在使用gcc 版本 10.2.0(Rev9,由 MSYS2 项目构建)。
解决方案
有一点微妙的是,friend
声明虽然不需要先前声明您的类所友好的函数或类,但不会使函数对查找可见,除非通过 ADL。
参考:
在类或类模板 X 中的友元声明中首先声明的名称成为 X 的最内层封闭命名空间的成员,但对于查找不可见(除了考虑 X 的参数相关查找),除非命名空间范围内的匹配声明已提供 - 有关详细信息,请参阅命名空间。
这就是为什么您能够找到bar(f)
(执行 ADL)但不能space::bar(f)
(完全限定名称意味着不调用 ADL)。
调用通过 ADL 找不到的代码bar
需要查看声明。在所有内容都在一个文件中的版本中,调用代码将看到space::foo
. 当您将其拆分为 HPP 和 CPP 文件时,调用代码只会看到提供有限可访问性的朋友声明。
正如您所确定的,如果您想通过普通查找使函数可见,请foo
在“foo.hpp”中声明:
#ifndef PROJECT_FOO_HPP
#define PROJECT_FOO_HPP
namespace space {
struct foo {
friend void bar(foo);
};
void bar(foo); // Now code that includes foo.hpp will see a declaration for bar
}
#endif //PROJECT_FOO_HPP