为什么友元函数不被视为在没有额外声明的情况下声明它的类的名称空间的成员?

Fur*_*ish 5 c++ namespaces friend header-files

假设我们有一个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"; }
}
Run Code Online (Sandbox Code Playgroud)

据我了解,friend void bar(foo); 声明 bar是一个按值获取space的自由函数foo。要使用它,我们可以简单地执行以下操作:

auto f = space::foo();
bar(f);
Run Code Online (Sandbox Code Playgroud)

我的理解是,我们不必说space::bar,因为 ADL 会看到 的定义与(其参数)bar 相同namespacefoo,并允许我们省略全名限定。尽管如此,我们仍然可以对其进行限定

auto f = space::foo();
space::bar(f);
Run Code Online (Sandbox Code Playgroud)

其工作原理(并且应该工作)完全相同。

当我引入其他文件时,事情开始变得奇怪。假设我们将类和声明移动到foo.hpp

#ifndef PROJECT_FOO_HPP
#define PROJECT_FOO_HPP

namespace space {
    struct foo {
        friend void bar(foo);
    };
}

#endif //PROJECT_FOO_HPP
Run Code Online (Sandbox Code Playgroud)

以及定义foo.cpp

#include "foo.hpp"
#include <iostream>

namespace space {
    void bar(foo f) { std::cout << "friend from a namespace\n"; }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我所做的只是将内容移动(没有更改任何代码)到.hpp-.cpp对。

然后发生了什么?好吧,假设我们#include "foo.hpp"仍然可以这样做:

auto f = space::foo();
bar(f);
Run Code Online (Sandbox Code Playgroud)

但是,我们不再能够这样做:

auto f = space::foo();
space::bar(f);
Run Code Online (Sandbox Code Playgroud)

这没有说:error: 'bar' is not a member of 'space',这很令人困惑。我相当确定他bar 的成员space,除非我严重误解了某些内容。同样有趣的是,如果我们另外声明(再次!)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
Run Code Online (Sandbox Code Playgroud)

现在可以了。

头文件/实现文件中是否有某些内容会改变预期的(至少对我来说)行为?这是为什么?这是一个错误吗?我正在使用gcc 版本 10.2.0(Rev9,由 MSYS2 项目构建)

Nat*_*son 4

有一个细微的差别friend,即该声明虽然不需要先前声明您的类所友好的函数或类,但不会使该函数对查找可见,除非通过 ADL。

cpp参考

首先在类或类模板 X 中的友元声明中声明的名称将成为 X 的最内层封闭命名空间的成员,但对于查找不可见(考虑 X 的参数相关查找除外),除非命名空间范围内有匹配的声明已提供 - 有关详细信息,请参阅命名空间。

这就是为什么您能够找到bar(f)(执行 ADL)但不能space::bar(f)(完全限定名称意味着不调用 ADL)。

调用未bar通过 ADL 找到的代码需要查看声明。在所有内容都在一个文件中的版本中,调用代码将看到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
Run Code Online (Sandbox Code Playgroud)