使用构造函数声明会损害访问说明符并且与其他类型的成员不一致

Using declaration of constructors compromises access specifiers and isn't consistent with other types of members

今天我了解到一个新的令人震惊的现实,所有流行的编译器(我可以动手的编译器,在 godbolt.org 上)都可以很好地处理这段代码(编译),我无法解释为什么:

class A
{
protected:
    A()
    { }
};

class B : private A
{
    using A::A;
};

int main()
{
    auto b = B{};
    return 0;
}

view on godbolt.org

我的推理:它应该在 auto b = B{}; 处失败,因为 using 声明在私有范围内,因此这是编译器隐式提供的构造函数所在的位置,作为 [=16] 的结果=], 应该去。

无论是任何其他成员:函数还是变量,它的访问修饰符都将根据 using 声明的放置位置来确定(public/protected/private节)。

但是,现在,这无法编译:

class A
{
protected:
    A(int)
    { }
};

class B : private A
{
    using A::A;
};

int main()
{
    auto b = B{1};
    return 0;
}

view on godbolt.org

这是可以预测和直观的:

<source>:15:14: error: calling a protected constructor of class 'A'
    auto b = B{1};
             ^
<source>:4:5: note: declared protected here
    A(int)

但是,不幸的是,由于其他原因,它没有编译(我相信这是直观的),因为这也不是:

class A
{
protected:
    A(int)
    { }
};

class B : public A
{
public:
    using A::A;
};

int main()
{
    auto b = B{1};
    return 0;
}

view on godbolt.org


using 声明似乎措辞不当或理解不当。不幸的是,许多编译器(幸运的是,其中一些编译器不再在 HEAD 中)也在为 friend 权限而苦苦挣扎:

class C;

class A
{
    friend class C;

protected:
    A(int)
    { }
};

class B : public A
{
public:
    using A::A;
};

class C
{
public:
    B make_b()
    {
        return B{1};
    }
};

int main()
{
    auto b = C{}.make_b();
    return 0;
}

view on godbolt.org

有语言律师能分析一下吗?我的假设错了吗?这应该是这样的?

如果 using 语句仅 导入 构造函数的声明但不会更改它们的 visibility,则存在问题在某种意义上 public,受保护的,私有的。

更稳健的方法是声明一个 public ctor:

class B : public A
{
public:
    B(int i): A(i){};
};

这样您就可以使用来自显式声明的 public 成员的受保护成员。


参考:

C++20 的 n4860 草案在 9.9 中使用声明 [namespace.udecl] §1

...If the using-declarator does not name a constructor, the unqualified-id is declared in the declarative region in which the using-declaration appears as a synonym for each declaration introduced by the using-declarator. If the using-declarator names a constructor, it declares that the class inherits the set of constructor declarations introduced by the using-declarator from the nominated base class.

那样只会声明非构造函数public。

原因是如果class中没有,编译器会添加一个默认的public无参构造函数。你的第一个case编译后是这样的:

class A
{
protected:
    A()
    { }
};

class B : private A
{
    using A::A;
public:
    B() : A() {}
};

int main()
{
    auto b = B{};
    return 0;
}

class.default.ctor

If there is no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted

classB 没有用户声明的构造函数。 B继承自A的构造函数不是B的构造函数,而是A的构造函数。在查找派生 class 的构造函数时会考虑继承构造函数,但它们仍然不是派生 class.

的构造函数

标准从未明确表示继承构造函数会或不会为派生 class 创建类似的构造函数。该标准确实表示基class 的构造函数可用于查找和重载解析,就好像 它们是派生class 的构造函数一样。这个 IMO 意味着它们不被视为派生 class 的构造函数,但如果标准明确说明会更好。无论如何,编译器似乎是这样解释的。

编辑 这是一个 change from C++14,其中继承的构造函数被注入派生的 class。即使在 C+14 中,这些构造函数也是隐式声明而不是用户声明的。

因此 B 有一个 public 隐式声明为默认的默认构造函数,无论它从 A.[=25 继承什么=]

namespace.udecl

Base-class constructors considered because of a using-declarator are accessible if they would be accessible when used to construct an object of the base class; the accessibility of the using-declaration is ignored.

因此 A::A(int) 在构造 B 时不可访问,即使导入它的 using 声明是可访问的。