C++ 为什么我的类是非默认可构造的?
我有这些课程:C++ 为什么我的类是非默认可构造的?,c++,C++,我有这些课程: #include <type_traits> template <typename T> class A { public: static_assert(std::is_default_constructible_v<T>); }; struct B { struct C { int i = 0; }; A<C> a_m; }; int main() { A<B::C&
#include <type_traits>
template <typename T>
class A {
public:
static_assert(std::is_default_constructible_v<T>);
};
struct B {
struct C {
int i = 0;
};
A<C> a_m;
};
int main() {
A<B::C> a;
}
一切都很好
使用Clang 9.0.0进行测试。它是:
如果上述模板的实例化依赖于,则直接或
间接地,在一个不完整的类型上,实例化可能会产生
如果假设该类型已完成,则会产生不同的结果
行为是未定义的
标准文本和注释中提到的几个主要实现都不允许这样做,但原因完全无关 首先,“按书”的原因:根据标准,
A
的实例化点是,,std::is_default_constructible
的实例化点就在该点之前:
对于类模板专门化,[…]如果专门化是
隐式实例化,因为它是从另一个内部引用的
模板专门化,如果专门化来自的上下文
是否引用取决于模板参数,以及
在实例化之前不会实例化专门化
在封闭的模板中,实例化点立即
在封闭模板的实例化点之前。
否则,这种专门化的实例化点
紧跟在命名空间范围声明或定义之前
这是指专业化
由于此时C
显然是不完整的,因此实例化std::is_default_constructible
的行为是未定义的。但是,请参见,这将改变此规则
实际上,这与NSDMI有关
- NSDMI很奇怪,因为它们得到了延迟解析——或者用标准说法,它们是“完整的类上下文”
- 因此,
原则上可以引用=0
中尚未声明的内容,因此在完成B
之前,实现无法真正尝试解析它B
- 完成类需要隐式声明特殊成员函数,特别是默认构造函数,因为
没有声明构造函数C
- 该声明的部分内容(constexpr ness、noexcept ness)取决于NSDMI的属性
- 因此,如果编译器无法解析NSDMI,则无法完成该类
- 因此,在实例化
时,它认为a
是不完整的C
处理延迟解析区域的整个领域都严重缺乏规范,伴随着实现上的分歧。清理之前可能需要一段时间。GCC 8.3-好,GCC 9.1/9.2-失败。使用
C(){}
也可以。我觉得这有点问题。Bugzilla上没有立即明显的匹配。有趣的是:A
中的static_断言
失败,但是如果您改为默认在A
内部构造一个T
(例如,将一个成员T;
放在那里),它就可以正常工作。类型特征告诉你的和实际可能发生的不一致…@Nicolas True,但这是因为一些边缘情况,这些情况在这里都不适用(特别是,正如cppreference上的同一句话所说,const int x;
在没有初始化器的情况下是无效的,这纯粹是因为const
和内置类型的初始化行为以及一些历史记录)为什么C不完整?@interjay,C
是完整的,但是B
不是。而且B::C
间接依赖于B
@Evg文本“直接或间接依赖”仅出现在cppreference.com上。标准只是说类型T需要完整。@T.C.很好的发现,看起来该文本是从C++17开始添加的。我不清楚“依赖”另一个类型意味着什么,以及它是否包括类嵌套。@interjay我写了大部分这句话。我们想说的是1)如果你以一种方式实例化了一个特征,当某个不完整的类型完成后,可能会引发ODR冲突,这是未定义的;2)它是未定义的,即使您实际上没有在程序中导致ODR冲突,因此标准库实现可以选择在使用该特性时进行诊断,如果他们愿意的话。如果C
有一个默认的构造函数模板,其中包含一些奇怪的SFINAE,如果B
的完成方式不同,则可以更改答案,当然,这一特性取决于它。我同意这个答案的第二部分(NSDMI),但不确定第一部分(“按书”的原因)。我认为“实例化点”只会影响依赖名称的查找,但是是\u default\u constructible
不需要查找任何依赖名称。我们可以在没有模板的情况下观察到相同的问题:
struct C {
int i;
};