考虑此代码:
#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上编译并打印A(1)
,这意味着B
的实例是“构造的”而不调用构造函数。如果取消注释C()
,则C c(1);
不再编译(GCC找不到合适的构造函数)
Clang没有说什么using B::A;
,但是拒绝编译C c(1);
(也找不到合适的构造函数)。
[MSVC就在using B::A;
处停止,基本上是说您只能从直接基继承构造函数。
Cppreference并未提及从间接基继承构造函数,因此似乎是不允许的。
是GCC和Clang错误,还是这里发生了什么?
我可以说,某些GCC继承该构造函数是错误的。主要是因为
[namespace.udecl]
3在用作成员声明的using声明中,每个 using-declarator的nested-name-specifier应命名为 被定义的类。如果using-declarator为构造器命名, 其嵌套名称说明符应命名该类的直接基类 正在定义。
但是最重要的是B::A
甚至都没有命名构造函数。
[[class.qual]
2在其中不忽略函数名称的查找中 nested-name-specifier提名一个类C:
- 如果在C中查询在嵌套名称说明符之后指定的名称,则是C的注入类名称(Clause [class]),或
- 在作为成员声明的using声明的using声明中,如果名称在 nested-name-specifier与标识符或 simple-template-id的模板名称在 嵌套名称说明符,
该名称被认为是为类C的构造函数命名。 [注意:例如,构造函数不是可接受的查找 导致一个详尽的类型说明符,因此构造函数将不会 用于代替注入的类名。 —尾注]这样的 构造函数名称应仅在a的declarator-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
中使用。我想这应该就像从基类中引入任何旧类型定义一样。即用Clang可以定义
C::A a(0);
哪个显示正确。唯一的用途是B
是否继承自protected A
。在这种情况下,默认情况下,注入的类名也将不可访问,直到使用using声明提出。在godbolt上修改您的示例即可确认。
MSVC可能对拒绝此代码非常热心。