在这个例子中,C++17 可以使用 Base 类的构造函数构造派生类{},但不能使用()。
这也适用于 GCC 和 Clang 的 C++20,但不适用于 MSVC。
为什么我可以这样构造派生类?安全吗?
我查看了 C++17 中的添加和更改,但找不到允许这样做的更改。
我很可能是瞎子。
#include <iostream>
class Base {
public:
Base(const int value) {
std::cout << "Constructed with value: " << value << '\n';
}
};
class Derived : public Base {
};
int main(){
// does compile on C++17 with MSVC
// does not compile pre or post C++17 with MSVC
// does compile on and post C++17 on GCC and Clang
// does not compile pre C++17 with GCC and Glang
Derived foo{ 42 };
// does not compile on any C++ version with MSVC, GCC or Clang
Derived bar(42);
}
Run Code Online (Sandbox Code Playgroud)
Nic*_*las 35
这是情况的快速概述:
Base可以从 隐式转换int。Base 不是聚合,因为它有一个用户提供的构造函数。Derived是不是从一个转换int(隐式或其他方式),因为基类的构造函数是不能继承的,除非你明确地继承他们(你没有)。Derived不是由于具有基类...的集合体在C ++ 14。Derived 是C++17 中的聚合,它允许聚合具有基类。Derived没有用户提供的任何构造函数;同样,Base的构造函数无关紧要,因为它没有被继承。鉴于这些事实,正在发生的事情如下。
尝试{}在类型上使用将首先(某种程度上)检查该类型是否是聚合;如果是这样,它将使用括号初始化列表中的值执行聚合初始化。由于DerivedC++14 和 C++17 之间的聚合是否发生了变化,因此初始化的有效性也发生了变化。
Per #4,Derived不是 C++14 中的聚合。所以列表初始化规则将尝试调用一个带有int. Per #3,Derived没有这样的构造函数。所以Derived foo{ 42 };是IL-形成在C ++ 14。
Per #5,Derived是 C++17 中的聚合。所以列表初始化规则将执行聚合初始化。这是通过由支撑初始化列表中的相应初始化程序复制初始化聚合的每个子对象来完成的。Derived只有一个类型为 的子对象,Base并且花括号初始化器列表只有一个初始值设定项:42。因此它将Base通过初始化程序执行复制初始化42。这将尝试执行从隐式转换int到Base,这是每#1有效的。
所以Derived foo{ 42 };在 C++17 中有效。
Visual Studio 可能没有正确实现 C++17 的规则集。