【问题标题】:There are some details I didn't understand in §7.3.1.2/3 in the C++11 Standard在 C++11 标准的 §7.3.1.2/3 中有一些我不理解的细节
【发布时间】:2014-05-29 17:41:25
【问题描述】:

C++11 标准中的第 7.3.1.2/3 节(重点是我的):

在命名空间中声明的每个名称first都是该名称空间的成员 命名空间。如果非本地类中的友元声明 first 声明 类或函数友元类或函数是 最里面的封闭命名空间。找不到朋友的名字 不合格查找 (3.4.1) 或合格查找 (3.4.3) 直到 在该命名空间范围内提供了匹配的声明( 在授予友谊的类定义之前或之后)。如果有朋友 函数被调用,它的名字可以通过名字查找找到 考虑来自与 函数参数的类型(3.4.2)。如果在朋友的名字 声明既不是限定的,也不是模板 ID 和声明 是一个函数或一个详细的类型说明符,查找来确定 该实体是否先前已申报,不应考虑任何 最里面的封闭命名空间之外的范围。 [注:其他 朋友声明的形式不能声明新成员 最里面的封闭命名空间,因此遵循通常的查找规则。

示例:

// Assume f and g have not yet been defined.
void h(int);
template <class T> void f2(T);
namespace A {
    class X {
        friend void f(X); // A::f(X) is a friend
        class Y {
            friend void g(); // A::g is a friend
            friend void h(int); // A::h is a friend
            // ::h not considered
           friend void f2<>(int); // ::f2<>(int) is a friend
        };
    };
    // A::f, A::g and A::h are not visible here
    X x;
    void g() { f(x); } // definition of A::g
    void f(X) { /* ... */} // definition of A::f
    void h(int) { /* ... */ } // definition of A::h
    // A::f, A::g and A::h are visible here and known to be friends
}
using A::x;
void h() {
    A::f(x);
    A::X::f(x); // error: f is not a member of A::X
    A::X::Y::g(); // error: g is not a member of A::X::Y
}

除非我遗漏了什么,否则我不明白上面 first 这两个词的必要性。据我所知,命名空间中的任何实体都不能有多个声明,类中的友元函数也不能有多个声明。

另外,示例中“假设 f 和 g 尚未定义”的注释有什么相关性?这些函数是否在命名空间 A 的定义之前声明并不重要。它们必然属于全局命名空间,与命名空间 A 内声明的函数无关。

编辑:

一个人可以重复声明同一个函数,或者在命名空间中声明和定义一个函数,这一事实并没有使我的观察无效,即在§7.3.1.2/3 不是必需的。

编辑1

我刚刚发现另一个错误。评论::f2&lt;&gt;(int) is a friend 不正确。不仅命名空间A中没有模板函数f2(T)的定义,更重要的是,声明template &lt;class T&gt; void f2(T);必须在inside A,否则函数f2&lt;&gt;(int)将不是它的朋友班级A::X::Y

【问题讨论】:

  • 我在参考/声明点上得到了纠正。我撤回了我的答案。
  • As far as I can see, you can't have more than one declaration of any entity in a namespace 您可以拥有任意数量的。 namespace N { void foo(); void foo(); void foo(); } 完全有效。虽然在这个例子中它也完全不相关,所以这不能回答你的问题。
  • On Edit1:我不知道你的测试是什么得出这个结论,但这个例子在这种情况下是正确的。朋友声明friend void f2&lt;&gt;(int); 使::f2&lt;int&gt; 成为内部类型的朋友。见this
  • @DavidRodríguez-dribeas 您的示例非常人为,令我惊讶的是它完全有效。例如,这个(coliru.stacked-crooked.com/a/e04d9ccf777564d7)应该可以编译,但它当然不会。现在看一个更“正常”的例子(coliru.stacked-crooked.com/a/5c97c2f36e21fbf3)。如果声明 template &lt;class T&gt; void f2(T); 不在命名空间内,则代码不会编译,这正是 §7.3.1.2/3 所要求的。
  • 也许在聊天中讨论这个会更容易。您提供的第一个链接失败有两个原因:函数名称中的拼写错误 (accesss) 以及 f&lt;int&gt; 的特殊化无法访问该函数(因为它不是朋友) .第二个确实编译,看here

标签: c++ c++11 namespaces language-lawyer friend


【解决方案1】:

希望比弗拉德的完整答案更简短更简洁:

一个实体可以多次声明,你的前提是错误的。在第一句中,first 很重要,因为这两个是命名空间 N 中函数 f 的有效声明:

namespace N { void f(); }
void N::f() { ... }        // A definition is *also* a declaration

此时第一句中对 first 的需求是显而易见的,fN 命名空间的成员(first 声明),而不是全局命名空间。

在朋友声明的情况下,first 很重要,原因不同,就像朋友声明是 first 声明一样,名称对于常规来说是不可见的查找:

//[1]
class Y {};                       // Some type
class X {
   X(Y);                          // allow implicit conversions, 
                                  //    for exposition purposes
   friend X operator+(X, X) {...} // *first* declaration of this operator
};                                //    and also the definition
void f() {
   Y a, b;
   a + b;                         // error, no operator+ takes two Y
   X c; 
   c + b;                         // OK, ADL can find it
}

如果朋友声明不是第一个声明,即如果 [1] 被之前的声明替换:

class X;
X operator+(X,X);

在所有其余代码相同的情况下,上述代码将编译并调用operator+(X,X),将ab 转换为X

您的最后一个问题是关于 假设 fg 尚未定义,我认为应该阅读已声明,而不是已定义。该声明的重要性在于,如果该函数事先已声明,则注释 // A::f, A::g and A::h are not visible here 变为 false,因为先前的声明使这些函数可见。

【讨论】:

  • The importance of this statement is that if the function have been declared before hand, the comment // A::f, A::g and A::h are not visible here becomes false, as the previous declaration makes those functions visible. 这是错误的。这个coliru.stacked-crooked.com/a/bb75f21c0469b7e4,也不是这个coliru.stacked-crooked.com/a/1475d2d598e2c223 编译。
  • @WakeupBrazil:您的代码没有提供 that 函数的声明(或定义),而是提供不同命名空间中的不同函数。见this
  • +1,我同意这个 IMO 是“首先在命名空间中声明”的最可能含义
【解决方案2】:

你错了。在一个声明区域中,您可能有多个相同函数的声明。例如

namespace N
{
   void f( int[10] );
   void f( int[10] );
   void f( int[] );
   void f( int[] );
   void f( int * );
   void f( int * );
}

所有这些声明都声明了同一个(也是一个)函数 f。

在您引用的引文中,first 表示友元函数首先在类定义中声明。类定义中的声明之前没有函数声明。

至于评论

// 假设 f 和 g 尚未定义

那么这意味着函数还没有被声明。那是他们的第一个声明在类定义中。

这部分引用

在命名空间中首先声明的每个名称都是该命名空间的成员 命名空间。如果非本地类中的友元声明首先声明 类或函数友元类或函数是 最里面的封闭命名空间

很清楚,在类定义中将函数声明为类的友元函数意味着它在封闭命名空间中的声明。但是,该函数在类定义之外也被声明之前是不可见的。

为了演示这个想法,请考虑以下示例。

#include <iostream>

struct A {
    friend void f();
};
void g() { f(); }
void f() { std::cout << "It is me!" << std::endl; }

int main() {
    return 0;
}

对于此代码,编译器发出错误

prog.cpp:在函数“void g()”中:prog.cpp:9:14:错误:“f”不是 在此范围内声明 void g() { f(); }

虽然函数 f 是在类 A 定义中声明的。如果在类定义之前没有任何函数声明,则该函数被视为定义类 A 的同一命名空间的成员(更准确地说,最好说明类的声明位置,因为可以定义类在一些封闭的命名空间中)。

但是,如果您再添加一个 f 声明,则代码将被编译。

#include <iostream>

struct A {
    friend void f();
};

void f();
void g() { f(); }
void f() { std::cout << "It is me!" << std::endl; }

int main() {
    return 0;
}

再考虑第三个例子

#include <iostream>

void f();
namespace N {
   struct A
   {
      friend void f();
   };
   void g() { f(); }
}
void f() { std::cout << "It is me!" << std::endl; }
int main() {
    return 0;
}

这里的函数 f 首先在命名空间 N 之前和类定义之前声明。因此,函数 f 的友元声明在该命名空间中的函数 g() 中不可见,直到友元函数在类外部重新声明。所以函数g会调用全局函数f。

最后考虑一个更有趣的例子

#include <iostream>

namespace N {
   class A;
}
class N::A {
    friend void f();
    int x = 10;
};

namespace N {
   //void f(); // if to uncomment the line the code will be compiled 
               // otherwise f will not be found
   void g() { f(); }
   void f() { A a; std::cout << "a.x = " << a.x << std::endl; }
}
int main() {
    return 0;
}

虽然类 A 的定义在全局命名空间中,但函数 f 是在命名空间 N 中声明的,这是该类首次声明的位置。

【讨论】:

  • 好吧,关于我的第一次观察,我错了。但在正文中仍然没有必要使用first这些词。
  • @Wake up Brazil 你又错了。在这种情况下,首先这个词非常重要。
  • @Wake up Brazil 查看我更新的帖子。它解释了一切。
  • 第三个例子是错误的,它可以编译但是下面的文本“所以函数的friend声明没有在namescope N中引入一个新名字”是错误的。该朋友声明与函数 N::f 成为朋友,而不是 ::f。它编译(和链接)的原因是N::fg 不可见,因此正常查找继续向外并找到::f,即不是该类型的朋友。 N::f 函数在任何上下文中都是不可见的,直到提供稍后的声明(如果有)。
  • 我已编辑代码示例以删除空格。虽然这通常是纯粹的风格,但我发现采用较少的屏幕真实状态可以更容易在问答网页上阅读(我会在文件上使用更多空间,使用适当的编辑器等等,但我觉得这读SO更好)。随时恢复部分或全部更改。
【解决方案3】:

请原谅我省略了这些例子,但我想保持简短和甜蜜。

first这个词的目的是强调友元或命名空间范围声明是函数在该点声明的唯一方式。该段落旨在讨论创建实体的声明,而不是稍后的匹配声明

也许最初会比first更好。

::f2&lt;&gt;(int) is a friend 的评论不正确。不仅在命名空间A中没有定义模板函数f2(T),而且更重要的是,声明template &lt;class T&gt; void f2(T);必须在A内部,否则函数f2&lt;&gt;(int)将不是类@的朋友987654327@。

::f2A::f2 不同。该示例的目的是强调这一点。 因为有一个模板::f2,不在A 中,它与friend void f2&lt;&gt;(int) 声明相匹配,所以该模板是其好友。只是因为没有可见的函数f 与声明friend void f(X); 匹配,它才会默认在namespace A 中创建一个新函数。

然而,这条规则相当复杂。注释前的最后一句意味着函数::f 仍然无法匹配friend void f(X);,因为该语言更喜欢向封闭的命名空间添加一些东西,并且只将外部命名空间作为最后的手段。所以它是这样的:

  1. 如果封闭的命名空间包含一个与friend 匹配的声明,请加好友。
  2. 否则,如果 friend 声明包含足够的信息来声明封闭命名空间中的某些内容,请创建该事物,但不要让合格或不合格的查找找到该声明。
  3. 否则,请在外部命名空间中查找匹配的声明。
  4. 否则,失败。

【讨论】:

  • 在最后一段:需要注意的是,对于模板 ID 和常规函数,查找是不同的。在 template-id 的情况下(即当找到 f2&lt;&gt;(int) 是一个特化的模板时)查找将转到外部范围,但在查找常规函数时它只会考虑最内部的命名空间。在示例中,结交f2&lt;&gt;(int) 将向外移动并找到模板,而结交h(int) 则不会在全局命名空间中找到h
  • 除此之外,+1 简明扼要地描述 first 的要点是为了强调 [...] 声明是函数的唯一方式那时已经声明了
  • @DavidRodríguez-dribeas Yikes。是的,我想编译器无法从专业化的友元声明中推断出模板声明。
  • 真正的区别在于friend声明是可以引入一个新实体还是必须引用一个现有实体。如果它可以引入新实体,它将在直接封闭的命名空间中这样做,并且不会尝试在该范围之外查找先前的声明。如果它不能引入一个新实体,那么它必须找到它所引用的(考虑模板特化,或者与不同类的成员函数交朋友)
  • @DavidRodríguez-dribeas 沿着这些思路更新,尽管我在你回复之前写了更新并且在发帖之前分心了:P
猜你喜欢
  • 1970-01-01
  • 2013-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-09
  • 2017-11-13
  • 1970-01-01
相关资源
最近更新 更多