为什么我的班级不是默认可构造的?


28

我有那些课:

#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;
}

编译时a_m不是默认可构造的,而是a可以构造的。

更改C为:

struct C {
      int i;
   };

一切安好。

使用Clang 9.0.0测试。


3
GCC 8.3-确定,GCC 9.1 / 9.2-失败。
2015年

2
有了C() {}它也可以。
2015年

3
这对我来说有点臭。在Bugzilla上没有立即明显的匹配。
Lightness Races in Orbit

2
有趣的是:static_assertin A失败了,但是如果您改为默认构造一个T内部对象A(例如,在其中放置一个成员T t;),则一切正常。类型特征告诉您的内容与实际可能的内容之间存在矛盾……
sebrockm

2
@Nicolas True,但这是由于某些极端情况,在这里没有一个适用(特别是,如cppreference上的同一句话所述,const int x;没有初始化程序是无效的,这完全是由于const内置类型的和初始化行为,以及一些历史)
轻轨比赛(

Answers:


9

标准文本和注释中提到的几种主要实现都不允许这样做,但是出于完全不相关的原因。

首先是“按书”的原因:A<C>根据标准, 实例化点紧接在定义之前B,而实例化点紧接在定义std::is_default_constructible<C>之前:

对于类模板专业化,如果由于从另一个模板专业化中对其进行引用而隐式实例化了该专业化,则引用该专业化的上下文取决于模板参数,并且该专业化没有在之前实例化对于封闭模板的实例化,实例化点紧接在封闭模板的实例化点之前。否则,此类专门化的实例化点紧接在引用该专门化的名称空间范围声明或定义之前。

由于此时C显然不完整,因此实例化的行为std::is_default_constructible<C>是不确定的。但是,请参阅核心问题287,它将更改此规则。


实际上,这与NSDMI有关。

  • NSDMI很奇怪,因为它们被延迟解析-或按照标准的说法,它们是“完整类上下文”。
  • 因此,= 0从原则上讲,它可以引用B尚未声明的内容,因此,实现在完成之前不能真正尝试解析它B
  • 完成类需要隐式声明特殊成员函数,尤其是默认构造函数,因为C没有声明构造函数。
  • 该声明的部分内容(constexpr-ness,noexcept-ness)取决于NSDMI的属性。
  • 因此,如果编译器无法解析NSDMI,则它将无法完成该类。
  • 结果,在实例化时A<C>,它认为C是不完整的。

整个处理延迟解析区域的区域都严重不足,伴随着实施上的分歧。可能需要一段时间才能清理干净。


0

未定义的行为是:

如果上述模板的实例化直接或间接取决于不完整的类型,并且假设该类型实例化完成,则该实例化会产生不同的结果,则该行为不确定。


7
为什么C不完整?
interjay

1
@interjay,C是完整的,但B不是。并B::C间接依赖B
16:37

1
@Evg文本“直接或间接依赖”仅出现在cppreference.com上。该标准只是说类型T需要完整。
interjay


2
@interjay我写了大部分这样的措辞。我们要说的是:1)如果您以某种方式实例化一个特征,以便稍后在某些不完整的类型完成时引发ODR违规,则该未定义;2)即使您实际上没有在程序中引起ODR违规,它也是未定义的,因此标准库实现可以根据需要选择诊断使用该特征。如果C具有带有一些奇怪SFINAE的默认构造函数模板,并且如果B完成方式不同,则可以更改答案,那么可以确定,特征取决于它。
TC
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.