在 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标准中的§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
在这一点上,显然需要在第一句中使用第一个,f
是N
命名空间(第一个声明)的成员,而不是全局命名空间。
在友元声明的情况下,第一个是重要的,原因不同,就好像友元声明是第一个声明一样,该名称对于常规查找是不可见的:
//[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)
,将a
和b
转换为X
。
您的最后一个问题是关于假设f
和g
尚未定义,我认为应该读作声明,而不是定义前声明,则注释// 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);
不匹配,因为该语言更喜欢在封闭的名称空间中添加一些内容,并且只将外部名称空间作为最后手段。所以它是这样的:
- 如果封闭的名称空间包含一个与
friend
匹配的声明,那么它就是朋友 - 否则,如果
friend
声明包含足够的信息来声明封闭命名空间中的某个东西,请创建该东西,但不要通过限定或非限定查找找到该声明 - 否则,请在外部命名空间中查找匹配的声明
- 否则,失败
- 我不明白为什么我声明一个空的内部结构并将其传递给构造函数
- 我不明白这段代码是如何对这个pythonlist()进行排序的,也不明白如何用C++中的向量来重现它
- 不明白迭代器,引用和指针失效,一个例子
- 而循环:简单的除法程序输出零,不明白为什么
- 'using namespace'实现细节的便捷方法(仅标头库)?
- 我不明白为什么我的代码不起作用并且需要更长的时间来运行
- 不明白这个程序的输出
- 我遇到了这个代码片段,不明白. 它递归检查 C++ 字符串中是否存在大写字符
- 什么是非营利组织???我的问题是我不明白为什么有人会使用它
- 我正在尝试用 c++ 制作菜单,但不明白为什么它不循环
- 我的程序不适用于 strcat - 我似乎不明白为什么?
- std::我不明白的矢量元素错误
- 我不明白 c++ 中的"cin"工作
- 我不明白参数和参数如何协同工作
- 什么'!((n % 5 != 0) ||(n % 20 == 0))'变身?为什么呢?我似乎不明白
- 在反转字符串'my.name.is'时,我得到的输出为"is@.name.my"。我不明白'@'是从哪里来的
- 不明白使用双指针 (**) 创建 2d 动态数组
- 我不明白为什么会编译
- 我不明白尝试使用字符串作为函数参数时遇到的错误
- 在 C++11 标准的 §7.3.1.2/3 中有一些我不明白的细节