在一个类中,`Using Base::BaseOfBase;‘应该做什么?
考虑以下代码:
#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的构造函数。 [?注意:例如,构造函数不是可接受的查找 结果产生精心设计的类型说明符,因此构造函数不会 用来代替注入的类名。?-?End Note?]这样的一个 构造函数名称只能在 在构造函数或Using-声明中命名的声明。 [?示例:
- 如果在C中查找嵌套名称说明符之后指定的名称是C的注入类名(子句[CLASS]),则
- 在作为成员声明的Using声明的Using声明符中,如果在 嵌套名称说明符与标识符或 的最后一个组件中的简单模板ID的模板名称 嵌套名称说明符,
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。
相关文章