在 C++11 标准的 §7.3.1.2/3 中有一些我不明白的细节

There are some details I didn't understand in §7.3.1.2/3 in the C++11 Standard

本文关键字:细节 明白 标准 C++11      更新时间:2023-10-16

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

命名空间中首先声明的每个名称都是命名空间。如果非本地类中的友元声明首先声明类或函数友元类或函数是最里面的封闭命名空间。找不到朋友的名字不合格查找(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
}

除非我遗漏了什么,否则我不明白是否需要上面的单词第一个。就我所见,在一个命名空间中,任何实体的声明都不能超过一个,在类中也不能有朋友函数的声明超过一个。

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

编辑:

事实上,同一个函数可以有重复的声明,或者在命名空间中有一个函数的声明和定义,这并没有使我的观察无效,即在§7.3.1.2/3中没有必要使用单词first

第1版

我刚刚发现另一个错误。注释::f2<>(int) is a friend不正确。命名空间A中不仅没有模板函数f2(T)的定义,而且更重要的是,声明template <class T> void f2(T);必须在A内部,否则函数f2<>(int)将不是类A::X::Y的朋友。

一个比Vlad的完整答案更短、更简洁的答案:

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

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

在这一点上,显然需要在第一句中使用第一个fN命名空间(第一个声明)的成员,而不是全局命名空间。

在友元声明的情况下,第一个是重要的,原因不同,就好像友元声明是第一个声明一样,该名称对于常规查找是不可见的:

//[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,因为前一个声明使这些函数可见。

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

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

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

在您引用的引号中,单词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的目的是强调友元或命名空间作用域声明是此时声明函数的唯一方式。本段旨在讨论创建实体的声明,而不是稍后的匹配声明

也许最初的会比最初的更好。

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

CCD_ 24与CCD_ 25不同。这个例子的目的是强调这一点因为有一个模板::f2,而不是在A中,它与friend void f2<>(int)声明匹配,所以该模板就是成为朋友的模板。只有因为没有与声明friend void f(X);匹配的可见函数f,它才会在默认情况下在namespace A中创建一个新函数。

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

  1. 如果封闭的名称空间包含一个与friend匹配的声明,那么它就是朋友
  2. 否则,如果friend声明包含足够的信息来声明封闭命名空间中的某个东西,请创建该东西,但不要通过限定或非限定查找找到该声明
  3. 否则,请在外部命名空间中查找匹配的声明
  4. 否则,失败