在现实生活中的项目中,我偶然发现了某些(某些版本)编译器的奇怪行为。考虑以下类声明:
struct OptionalsStruct {
struct InnerType {
bool b{};
};
OptionalsStruct() = default;
std::optional<InnerType> oInnerType;
};
对于一些编译器,你有
OptionalStruct::InnerType
是 nothrow constructible 但不是 constructible 或 default constructible(clang 11 到 16 和 GCC 10),对于其他一些编译器它既不是 nothrow可构造(clang 9和10),更不用说clang 8如何看待整个事情了。
我的问题是:这些行为是编译器错误,还是标准中的漏洞(我使用的是 C++17)?我在这里错过了什么吗?
直到最外层封闭类的
Inner
之后,编译器才会将}
视为“完全完整”,因为完整类上下文规则要求编译器延迟在默认成员初始值设定项中查找b
直到那一点之后,但是封闭类的特殊成员函数的(隐式)声明的某些属性可能间接依赖于默认成员初始值设定项的属性。
有了这个解释,你有未定义的行为来实例化
std::optional
使用不完整的类型作为模板参数。
实际上,
std::optional
的实例化可能会导致您在 main
中使用的类型特征特化的实例化,具体取决于 std::optional
的实现。如果是这样,那么类型特征特化是用不完整的类型实例化的,这也会导致未定义的行为并且实际上无法产生合理的结果。
因为类模板特化只被实例化一次(并且编译器可以缓存结果),你以后对特征的使用依赖于之前隐式实例化的无意义结果。
据我所知,嵌套类的完整性未明确指定是标准中的一个开放缺陷,并且不清楚编译器的解释是否“正确”。
(从非常迂腐的角度来看,它无论如何都是符合标准的 UB,因为
std::optional<InnerType>
的实例化点将在导致隐式实例化的名称空间范围声明之前,即在 struct OptionalsStruct {
之前,其中 InnerType
肯定是不完整。请参阅 CWG 问题 287。)