使(虚拟)函数在中间基 class 中不可访问,在大多数派生 class 中可访问,定义明确吗?

Making (virtual) functions made innaccessible in intermediate base class accessible in most-derived class, well-defined?

考虑以下代码:

#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++clangmsvc 进行编译和工作,即打印 b::f().

但是,如果我将 ab 替换为:

struct a {
    void f() { }
};

struct b: a {
private:
    using a::f;
};

...代码不再使用 gcc 进行编译,但使用 clangclang-clmsvc 编译罚款(感谢 StoryTeller 和 Adrian Mole)。我收到以下错误(在 c 中的 using a::f; 行):

error: 'void a::f()' is inaccessible within this context

我在标准中找不到关于 using a::f;(在 c 中)在这些情况下的行为的明确点,那么标准是否明确定义了上述内容?

注意: 我不是在谈论在 class 的 public 范围内引入某些东西(比如 using b::fc if b::f was protected),但真正让来自顶级 class 的成员在最派生的 class 中可访问,因为这些成员在中间基础 [=48] 中不可访问=].

我认为 GCC 拒绝修改后的代码是错误的。

[namespace.udecl]

1 Each using-declarator in a using-declaration introduces a set of declarations into the declarative region in which the using-declaration appears. The set of declarations introduced by the using-declarator is found by performing qualified name lookup ([basic.lookup.qual], [class.member.lookup]) for the name in the using-declarator, excluding functions that are hidden as described below.

3 In a using-declaration used as a member-declaration, each using-declarator's nested-name-specifier shall name a base class of the class being defined. If a using-declarator names a constructor, its nested-name-specifier shall name a direct base class of the class being defined.

首先我要注意第 3 段区分了基数和直接基数。因此我们可以在 using 声明中命名 a::f。其次,根据第 1 段,名称查找按预期进行

[class.qual]

1 If the nested-name-specifier of a qualified-id nominates a class, the name specified after the nested-name-specifier is looked up in the scope of the class ([class.member.lookup]), except for the cases listed below. The name shall represent one or more members of that class or of one of its base classes (Clause [class.derived]).

[class.member.lookup]

1 Member name lookup determines the meaning of a name (id-expression) in a class scope. Name lookup can result in an ambiguity, in which case the program is ill-formed. For an id-expression, name lookup begins in the class scope of this; for a qualified-id, name lookup begins in the scope of the nested-name-specifier. Name lookup takes place before access control.

所以 a::f 只能在 a 的范围内或者它自己的基础 class 中查找。根本不应该在 b 中查找它。因此,我认为在进行限定名称查找时,b 中名称 f 的可访问性不应影响 a 中名称 f 的可访问性。

a中,f是public。因此可以在可以命名 a 的任何声明区域中通过限定 ID 命名。其中包括 c。因此 using 声明为基 class 的有效成员使用了有效名称。在该声明区域中可以访问。因此它是有效的。

作为另一个数据点,GCC在其他用途​​上a::f的可访问性没有问题。 For example,GCC 允许在 c.

范围内形成指向 a::f 成员的指针
struct c: b {
public:
    c() {
        [[maybe_unused]] auto f = &a::f;
    };
};

所以它显然不认为名称 a::f 由于 b 在所有上下文中都不可访问。