这个问题的灵感来自于我的示例中的继承层次结构有什么问题?
考虑我们有一个结构体
B
,它可以通过对另一个结构体A
的const引用来构造。 B
可以从同时继承 A
和 B
的对象构造吗?
struct A {};
struct B {
B() {}
B(const A&) {}
};
struct C : A, B {};
B b1(C{}); // 1. ok everywhere
C c;
B b2(c); // 2. ok in MSVC, error in GCC and Clang
所有编译器都接受从临时构造
B b1(C{});
的第一个选项。从左值 B b2(c);
构造的第二个选项仅被 MSVC 接受,而 GCC 和 Clang 都因歧义错误而拒绝它:
error: call of overloaded 'B(C&)' is ambiguous
note: candidate: 'B::B(const A&)'
note: candidate: 'constexpr B::B(const B&)'
在线演示:https://godbolt.org/z/hTM6c5M73
GCC 和 Clang 接受案例 1 真的正确吗?如果在
B
: 中另外声明默认复制构造函数,他们改变主意是否正确?
B(const B&) = default;
B b2(c);
是不明确的,因为 B
有两个可行的构造函数:
B(const A&);
B(const B&); // implicitly-declared copy constructor
// implicitly-declared move constructor not viable
两者都将
c
左值直接绑定到其参数,但具有每个 [over.ics.ref]/1 的派生到基数转换序列(而不是身份序列)的等级。转换序列在最佳重载方面是无法区分的,因为 A
都不是从 B
派生的,也不是 B´ derived from
A` 派生的。 [over.ics.rank]/3 和 [over.ics.rank]/4 中的规则均无法区分它们。
B b1(C{});
是明确的,因为还有另一个可行的重载
B(B&&); // implicitly-declared move constructor
这将参数直接绑定到右值引用,使其被认为比每个 [over.ics.rank]/3.2.3.
的其他两个重载更好添加
B(const B&) = default;
通过删除 B(B&&);
移动构造函数来修改重载集,因为如果每个 [class.copy.ctor]/8.1. 有用户声明的复制构造函数,则不会隐式声明它。
然后
B b1(C{});
与之前一样在其他两个剩余重载之间是不明确的。