在 class 中,`using Base::BaseOfBase;` 应该做什么?

In a class, what is `using Base::BaseOfBase;` supposed to do?

考虑这段代码:

#include <iostream>
#include <type_traits>

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

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

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 和 Clang 的错误,还是这里发生了什么?

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

[namespace.udecl]

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.

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

[class.qual]

2 In a lookup in which function names are not ignored and the nested-name-specifier nominates a class C:

  • if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (Clause [class]), or
  • in a using-declarator of a using-declaration that is a member-declaration, if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the nested-name-specifier,

the name is instead considered to name the constructor of class C. [ Note: For example, the constructor is not an acceptable lookup result in an elaborated-type-specifier so the constructor would not be used in place of the injected-class-name.  — end note ] Such a constructor name shall be used only in the declarator-id of a declaration that names a constructor or in a using-declaration. [ Example:

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

 — end example ]

以上两条都不适用。所以 B::A 不是构造函数的名称。它只是注入的 class 名称 A,它已经可以在 C 中使用。我猜它应该就像从基础 class 引入任何旧类型定义一样。 IE。 Clang 会让你定义

C::A a(0);

这似乎是正确的。如果 B 是从 protected A 继承的,它的唯一用处是。在这种情况下,注入的 class 名称在默认情况下也是不可访问的,直到通过 using 声明提出。在 godbolt 上修改你的例子证实了这一点。

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

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

GCC 确实在这样做(因为使 c'tors 显式化会拒绝代码),而 Clang 似乎还没有实现 P0960

C c(1);

在 C++20 模式下被 GCC 正确接受,因为 C 是一个集合:它没有任何自己的或继承的构造函数。 C++20 的新特性是“聚合的括号化初始化”:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0960r3.html,它允许我们进行这样的初始化。

Clang 尚未实现此功能,因此它拒绝代码,请参阅 https://en.cppreference.com/w/cpp/compiler_support

并且如果您在 C 中添加任何构造函数,例如

struct C : B
{
    C() = default;
    using B::A;
};

那么 GCC 也会拒绝该代码,因为 C 不再是聚合,演示:https://gcc.godbolt.org/z/89hTGh4eh