在一个类中,`Using Base::BaseOfBase;‘应该做什么?

2022-05-16 00:00:00 c++ c++20

考虑以下代码:

#include <iostream>
#include <type_traits>

struct A
{
    A(int x) {std::cout << "A(" << x << ")
";}
};

struct B : A
{
    using A::A;
    B(int x, int y) : A(x) {std::cout << "B(" << x << "," << y << ")
";}
};

struct C : B
{
    using B::A; // <--

    // C() : B(0,0) {}
};

int main()
{
    C c(1);
}

gcc.godbolt.org

它在GCC上编译并打印A(1),这意味着B的实例是在没有调用构造函数的情况下构造的。如果取消注释C(),则C c(1);不再编译(GCC找不到合适的构造函数)

Clang对using B::A;没有任何说明,但拒绝编译C c(1);(也找不到合适的构造函数)。

MSVC直接停在using B::A;,基本上就是说只能从直接基继承构造函数。

Cppreference未提及从间接基继承构造函数,因此似乎是不允许的。

是GCC的漏洞,还是这里发生了什么?


解决方案

构造函数未继承。主要是因为

[Namespace.udecl]

3在用作成员声明的使用声明中,每个 Using-声明器的嵌套名称说明符应该命名 正在定义的类。如果使用声明器命名构造函数, 它的嵌套名称说明符应该命名类的直接基类 正在定义。

但最重要的是B::A根本没有命名构造函数。

[class.qual]

2在不忽略函数名的查找中 嵌套名称说明符指定类C:

  • 如果在C中查找嵌套名称说明符之后指定的名称是C的注入类名(子句[CLASS]),则
  • 在作为成员声明的Using声明的Using声明符中,如果在 嵌套名称说明符与标识符或 的最后一个组件中的简单模板ID的模板名称 嵌套名称说明符,
取而代之,该名称被视为命名类C的构造函数。 [?注意:例如,构造函数不是可接受的查找 结果产生精心设计的类型说明符,因此构造函数不会 用来代替注入的类名。?-?End Note?]这样的一个 构造函数名称只能在 在构造函数或Using-声明中命名的声明。 [?示例:

struct A { A(); };
struct B: public A { B(); };

A::A() { }
B::B() { }

B::A ba;            // object of type A
A::A a;             // error, A?::?A is not a type name
struct A::A a2;     // object of type A

?-?结束示例?]

以上两个项目符号都不适用。因此B::A不是构造函数的名称。它只是注入的类名A,已经可以在C中使用。我想这应该就像从基类引入任何旧类型定义一样。也就是说,点击可以让您定义

C::A a(0);

看起来是正确的。唯一的用处是如果B继承自protected A。在这种情况下,注入的类名在默认情况下也是不可访问的,除非使用Using声明。在godbolt上修改您的示例可以确认这一点。

MSVC可能过于热衷于直接拒绝此代码。

至于哪个编译器是正确的,C++20引入了aggregate initialization via parenthesized list of values。C是聚合,所以C c(1);实际上是使用1复制初始化B子对象的聚合初始化c。因此,C无需继承任何构造函数即可使此代码有效。

GCC确实在这么做(因为显式构造函数会拒绝代码),而Clang似乎还没有实现P0960。

相关文章