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

Why is a friend function not treated as a member of a namespace of a class it was declared in without additional declaration?

假设我们有一个来自命名空间 space 的 class foo,它声明了一个名为 barfriend 函数,稍后将定义它,例如所以:

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中定义19=](它的参数)并允许我们省略全名限定。尽管如此,我们被允许对其进行限定:

auto f = space::foo();
space::bar(f);

哪个有效(并且应该有效)完全相同。

当我引入其他文件时,事情开始变得奇怪。假设我们将 class 和声明移动到 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 的成员,除非我严重误解了某些内容。同样有趣的是,如果我们另外声明(再次!)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 声明有一点微妙之处,虽然它不需要先前的函数声明或 class 你的 class 是友好的,但不会使函数除了通过 ADL 之外的查找可见。

cppreference:

A name first declared in a friend declaration within a class or class template X becomes a member of the innermost enclosing namespace of X, but is not visible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided - see namespaces for details.

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

通过 ADL 找不到 bar 的调用代码需要查看声明。在所有内容都在一个文件中的版本中,调用代码将看到 space::foo 的完整定义。当您将其拆分为一个 HPP 和一个 CPP 文件时,调用代码只能看到提供有限可访问性的友元声明。

如您所见,如果要通过普通查找使函数可见,请在“foo.hpp”中放置 foo 的声明:

#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