【问题标题】:In a class, what is `using Base::BaseOfBase;` supposed to do?在一个类中,`using Base::BaseOfBase;` 应该做什么?
【发布时间】:2021-11-16 14:12:33
【问题描述】:

考虑这段代码:

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

【问题讨论】:

  • 根据 C++ 标准,您只能将 using 声明用于直接基类的构造函数。
  • 我认为值得在这里添加c++20 标签,因为在这个例子中,GCC 的行为在 C++17 和 C++20 模式下明显改变。

标签: c++ c++20


【解决方案1】:

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

[namespace.udecl]

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

但问题是 B::A 甚至根本没有命名构造函数。

[class.qual]

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

  • 如果在嵌套名称说明符之后指定的名称,当在 C 中查找时,是 C 的注入类名称(子句 [类]),或者
  • 在作为成员声明的 using-declaration 的 using-declarator 中,如果在 嵌套名称说明符与标识符或 simple-template-id 的模板名称在最后一个组件中 嵌套名称说明符,

该名称被认为是命名类 C 的构造函数。 [ 注意:例如,构造函数是不可接受的查找 产生一个详细的类型说明符,因此构造函数不会 用于代替注入的类名。 —— 尾注 ]这样一个 构造函数名称只能用于 命名构造函数或使用声明的声明。 [ 示例:

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 中使用。我想这应该就像从基类中引入任何旧的类型定义一样。 IE。 Clang 会让你定义

C::A a(0);

这看起来是正确的。这样做的唯一用途是如果B 是从protected A 继承的。在这种情况下,默认情况下注入的类名也将不可访问,直到使用 using 声明。在godbolt 上修改您的示例即可确认。

MSVC 可能过于热衷于彻底拒绝此代码。

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

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

【讨论】:

    【解决方案2】:

    线

    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

    【讨论】:

      猜你喜欢
      • 2011-02-23
      • 2013-03-29
      • 1970-01-01
      • 1970-01-01
      • 2013-05-12
      • 2022-01-22
      • 2015-09-17
      • 1970-01-01
      相关资源
      最近更新 更多