考虑以下代码。
#include <iostream>
struct a {
virtual void f() = 0;
};
struct b: a {
private:
void f() override {
std::cout << "b::f()\n";
}
};
struct c: b {
public:
using a::f;
};
int main() {
::c c;
c.f();
}
这段代码在编译时和预期的一样 g++
, clang
和 msvc
即打印 b::f()
.
但是,如果我把 a
和 b
通过这个。
struct a {
void f() { }
};
struct b: a {
private:
using a::f;
};
...代码不能再编译了... gcc
但在编制罚款的时候,却要同时考虑到 clang
, clang-cl
和 msvc
(感谢StoryTeller和Adrian Mole)。我得到以下错误(在 using a::f;
一行 c
):
错误:''在此上下文中无法访问
void a::f()
'在此上下文中是不可访问的
我在标准中找不到一个明确的点,关于行为的 using a::f;
(在 c
)在这些情况下,那么上述情况是否被标准很好地定义了呢?
请注意。 我不是说要把一些东西带进 public
作用域 using b::f
在 c
如果 b::f
是受保护的),但实际上是在中间基类中的成员被禁止访问后,让最派生类中的成员可以被访问。
我认为GCC拒绝修改代码是错误的。
[namespace.udecl]
1 使用声明中的每个使用声明者都会向使用声明出现的声明区域引入一组声明。通过对使用-声明符中的名称进行限定名查找([basic.lookup.qual],[class.member.lookup]),可以找到使用-声明符引入的声明集,其中不包括下面描述的隐藏的函数。
3 在作为成员声明的使用声明中,每个使用声明者的嵌套名称说明符都应命名被定义的类的基类,如果一个使用声明者命名一个构造函数,它的嵌套名称说明符应命名被定义的类的直接基类。如果一个使用声明者命名了一个构造函数,那么它的嵌套名称说明符应直接命名一个被定义的类的基类。
所以首先我要注意到,第3段对基类和直接基类进行了区分。因此,我们可以命名为 a::f
的使用声明中。其次,根据第1段,名称查找的过程与人们预期的一样。
[class.qual]
1 如果一个限定id的嵌套名称指定符指定了一个类,那么在嵌套名称指定符之后指定的名称将在该类的范围内被查找([class.member.lookup]),但下面列出的情况除外。该名称应代表该类的一个或多个成员或它的一个基类的成员(条款[class.derived])。
类.成员.查找
1 成员名称查找确定类作用域中名称(id-表达式)的含义。名称查找可能会导致歧义,在这种情况下,程序是不规范的。对于一个id-表达式,名称查找从这个类的作用域开始;对于一个限定的id,名称查找从嵌套的name-specifier的作用域开始。名称查找发生在访问控制之前。
所以 a::f
的范围内进行查询。a
或自己的基类。它根本不应该在 b
. 我认为,因此,该名称的无障碍性是非常重要的。f
在 b
不应影响名称的可访问性 f
在 a
在进行限定名称查询时。
在 a
, f
是公共的。所以可以在任何声明区域中用限定的id来命名,其中 a
可以命名。这包括 c
. 所以using声明使用了一个有效的名称来表示一个基类的有效成员。这在声明区域中是可以访问的。因此它是有效的。
作为另一个数据点,GCC对以下的可访问性没有问题 a::f
在其他用途上。例如,GCC允许形成一个指向成员的指针,以实现 a::f
范围内 c
.
struct c: b {
public:
c() {
[[maybe_unused]] auto f = &a::f;
};
};
所以,它显然不认为这个名字 a::f
在所有情况下都无法访问,原因是 b
.