【问题标题】:C++ inline definition of friend function友元函数的C++内联定义
【发布时间】:2019-03-22 21:48:34
【问题描述】:

在 C++ 标准的当前草案(2019 年 3 月)[class.friend] p.6 状态(强调我的):

当且仅当该类是非本地类([class.local]),函数名称是非限定的,函数具有命名空间范围时,才能在类的友元声明中定义函数。 [...]

“函数具有命名空间范围”是什么意思?

我能想到的函数没有命名空间范围的唯一情况如下:

struct A
{
    static void f();

    struct B
    {
        friend void f() {}; 
    }; 
};

然而,clanggcc 都没有将B 中的friend 定义A 中的静态方法相关联,而是将属于全局命名空间的函数。

还有其他我想念的情况吗?

【问题讨论】:

    标签: c++ language-lawyer friend


    【解决方案1】:

    我认为您实际上已经回答了自己的问题,但没有意识到。

    “函数具有命名空间范围”意味着它是命名空间的一部分,而不是类或结构的一部分。所以函数 A::B::f() 不存在。它也没有引用 A::f() 。相反,您定义为友元的函数实际上是函数 ::f(),因为这是它所在的命名空间(全局命名空间)。

    我怀疑(但没有尝试过),如果您将所有这些包装在一个命名空间中,那么您定义的 f() 将成为该命名空间的一部分。所以,例如,

    namespace ns {
        struct A
        {
            static void f();
    
            struct B
            {
                friend void f() {}; 
            }; 
        };
    }
    

    将友元函数定义为函数 ns::f()。

    【讨论】:

    • 我认为朋友定义不应该定义 ns::f,因为标准规定:“朋友声明指定的名称应在包含朋友声明的类范围内可访问。”。所以它首先搜索一个函数,它找到了,因为friend声明实际上是一个定义,所以程序不应该编译,而不是定义ns::f。
    • @user42768 标准中的“可访问”是指public/protected/private 访问级别。那句话是说一个类X只有当名称Y::gpublic,或者protectedX继承Y,或者Y声明类X时,才能拥有friend void Y::g();一个朋友。 (这种情况也不能作为定义。)
    • 朋友定义首先是一个声明。声明引入名称,它们(通常)不会导致查找被声明的名称。我认为这里适用的是[namespace.memdef]/3,基于此,上面的朋友声明似乎确实包含ns::f的定义,而原始示例中的定义只是定义了函数::f。您可以通过简单地添加::f() 的定义来验证这一点,并观察这将导致由于重新定义而导致编译错误...
    • 另外值得注意的是:标记为friend 的函数定义确实定义了一个命名空间成员,但实际上它本身并没有在命名空间中声明该函数。所以在这个例子之后,你不能打电话给ns::f();。但是如果你在命名空间中添加声明void f();或在命名空间之外添加void ns::f();,它会命名已经定义的相同函数,因此它可以被调用。
    • 然而,不幸的是,这似乎并没有真正回答最初的问题,即为什么函数具有命名空间范围的限制首先存在!?
    【解决方案2】:

    寻找相关的规则说friend 定义一个不合格的函数名总是一个命名空间成员,我发现情况并非如此。 [命名空间.memdef]/3:

    如果非本地类中的friend 声明首先声明了一个类、函数、类模板或函数模板,则友元是最内层封闭命名空间的成员。 ... 如果 friend 声明中的名称既不是限定的也不是 template-id 并且该声明是一个函数或一个 elaborated-type-specifier,则查找确定实体是否先前已声明不应考虑最内层封闭命名空间之外的任何范围。

    所讨论的要求仅适用于函数定义,并且排除了本地友好类或限定的朋友名称。但这留下了 template-id 作为函数名的可能性。

    因此,这段代码的措辞似乎有所不同:

    struct A {
        template <typename T>
        static void f();
    
        template <typename T>
        struct B {
            friend void f<T>() {}
        };
    };
    

    这里友元声明中的名字是一个template-id,所以关于跳过非命名空间作用域的规则不适用,f真正将函数模板命名为A::f。所以 [class.friend]/6 说这是格式错误的,但如果 friend 声明不是一个定义,它会是格式正确的。

    【讨论】:

    • "如果朋友声明中的名称既不是限定词也不是模板ID,并且声明是函数或详细类型说明符,则查找以确定实体是否先前已声明过不考虑最里面的封闭命名空间之外的任何范围。”可以理解为,查找考虑了朋友声明和最里面的封闭命名空间之间的所有范围,但不考虑该范围之外的任何范围(与内部相反,不是“除外”的同义词)。
    • 另外,B 中的朋友定义是否定义了 f 的显式特化?然后它需要template&lt;&gt; 语法。还有其他规则禁止这样做,例如:“显式特化应在模板所属的命名空间中声明,或者,对于成员模板,应在封闭类或封闭类模板所属的命名空间中声明.".
    • 友元定义没有定义显式特化,因为“显式特化”被定义为以template&lt;&gt; 开头的声明。它只是“函数模板专业化的定义”。当然没有这样的东西,但是规则允许朋友声明命名一个函数模板特化而不暗示它是明确特化或实例化的,而且 [class.friend]/6 是我看到的唯一一个地方说这样的声明可能不是一个定义。
    • 好的,但请考虑以下示例:wandbox.org/nojs/gcc-head/permlink/zDsAt43GCW3HSxV4。 [class.friend]/6 不适用,但程序仍然格式错误。我认为这两个示例都无效,因为您无法定义模板专业化,并且与它们是朋友声明无关。
    • 我明白你关于 [namespace.memdef]/3 范围规则的观点,但这只是提出了一个额外的问题,即为什么编译器将原始示例中的朋友视为新的 ::f,而不是A::f 的重新声明。是的,non-template-id 朋友定义是另一个好点。我仍然看不到任何明确禁止这样做的内容,但与部分说“可以”声明显式特化和显式实例化相比,这可能暗示它因遗漏而无效。
    猜你喜欢
    • 2016-11-14
    • 2017-10-26
    • 1970-01-01
    • 1970-01-01
    • 2013-07-25
    • 1970-01-01
    • 2020-04-10
    • 2012-01-02
    • 1970-01-01
    相关资源
    最近更新 更多