在 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 上编译并打印 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 values。 C
是一个聚合,所以 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
考虑这段代码:
#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 的错误,还是这里发生了什么?
构造函数不被继承。主要是因为
[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 values。 C
是一个聚合,所以 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