`std::default_initializable` 和 `std::is_default_constructible` 有什么区别?

What is the difference between `std::default_initializable` and `std::is_default_constructible`?

C++20 添加了一个名为 std::default_initializable. The difference in naming compared to std::is_default_constructible 的概念,似乎太明显了,不可能是偶然的。

他们的规范措辞也不同(至少在 cppreference 上),但是我不明白 有效 差异是什么。

以下是 cppreference 文章的简短摘要:

std::is_default_constructible<T> 要求 T foo(); 格式正确,假设它没有被解析为函数声明。换句话说(如果我理解正确的话),T 需要是可破坏的并且 T() 需要是合式的。
(编辑:它似乎不需要破坏性,所以这是第一个区别)

另一方面,

std::default_initializable 要求 T foo;T()T{} 格式正确。

这两者之间有什么有效的区别吗?

据我所知,T{} 总是被解释为 T()(忽略最烦人的解析,这不算在这里),所以我不确定为什么明确提到它。另外 T() 格式正确似乎暗示 T foo; 格式正确。

LWG 问题 3338

强调 is_default_constructible 特性与最初命名为 default_constructible 的 C++20 概念之间的含义差异,LWG issue 3149 被接受:

3149 DefaultConstructible should require default initialization

Discussion

[...] [the concept] DefaultConstructible<T> requires that objects of type T can be value-initialized, rather than default-initialized as intended.

The library needs a constraint that requires object types to be default-initializable. [...]

Users will also want a mechanism to provide such a constraint, and they're likely to choose DefaultConstructible despite its subtle unsuitability.

Tim Song 提供了一个例子,说明什么时候“可以被值初始化”的要求与更严格的“可以被默认初始化”的要求相比太弱了。

Tim Song noted that {} is not necessarily a valid initializer for a DefaultConstructible type. In this sample program (see Compiler Explorer):

struct S0 { explicit S0() = default; };
struct S1 { S0 x; }; // Note: aggregate
S1 x;   // Ok
S1 y{}; // ill-formed; copy-list-initializes x from {}

S1 can be default-initialized, but not list-initialized from an empty braced-init-list. The consensus among those present was that DefaultConstructible should prohibit this class of pathological types by requiring that initialization form to be valid.

问题 3149 已移至 WP 状态(除技术勘误外,基本上已被接受)。

问题 3338 随后也被赋予了 WP 状态,将 default_constructible 概念重命名为 default_initializable:

3338. Rename default_constructible to default_initializable

Discussion

[...] It was made clear during discussion in LEWG that 3149 would change the concept to require default-initialization to be valid rather than value-initialization which the is_default_constructible trait requires. LEWG agreed that it would be confusing to have a trait and concept with very similar names yet slightly different meanings [...].

Proposed resolution:

[...] Change the stable name "[concept.defaultconstructible]" to "[concept.default.init]" and retitle "Concept default_constructible" to "Concept default_initializable". Replace all references to the name default_constructible with default_initializable (There are 20 occurrences).

简而言之 std::default_initializable<T> 需要 std::is_default_constructible<T> && std::destructible<T>,以及一些默认构造的极端情况。

正在查看 spec

template<class T> 
concept default_initializable = 
    std::constructible_from<T> && 
    requires { T{}; } && 
    requires { ::new (static_cast<void*>(nullptr)) T; };

而对于 std::is_default_construtible,规范 defines

If std::is_constructible<T>::value is true, provides the member constant value equal to true, otherwise value is false.

进一步研究 default_initializable 的定义,规范 defines

template < class T, class... Args >
concept constructible_from =
    std::destructible<T> && 
    std::is_constructible<T, Args...>::value;

因为我们只看std::constructible_from<T>那么我们可以看到default_initializable的定义可以重写为

template<class T> 
concept default_initializable = 
    std::is_constructible<T>::value &&
    std::destructrible<T> && 
    requires { T{}; } && 
    requires { ::new (static_cast<void*>(nullptr)) T; };

最后是

template<class T> 
concept default_initializable = 
    std::is_default_constructible<T>::value &&
    std::destructrible<T> && 
    requires { T{}; } && 
    requires { ::new (static_cast<void*>(nullptr)) T; };

这基本上是LWG 3149:

DefaultConstructible<T> is equivalent to Constructible<T> (18.4.11 [concept.constructible]), which is equivalent to is_constructible_v<T> (20.15.4.3 [meta.unary.prop]). Per 20.15.4.3 [meta.unary.prop] paragraph 8:

The predicate condition for a template specialization is_­constructible<T, Args...> shall be satisfied if and only if the following variable definition would be well-formed for some invented variable t:

T t(declval<Args>()...);

DefaultConstructible<T> 要求类型 T 的对象可以 value-initialized,而不是 default-initialized 如预期。

这个概念的动机是检查你是否可以写:

T t;

但是定义不检查那个,它检查你是否可以写 T()。但是 T() 也不意味着你可以写 T{} - 有些类型具有不同的含义:

struct S0 { explicit S0() = default; };
struct S1 { S0 x; }; // Note: aggregate
S1 x;   // Ok
S1 y{}; // ill-formed; copy-list-initializes x from {}

为了理智起见,我们的目的是简化图书馆必须处理的事情,所以我们想拒绝 S1 只是因为它很奇怪。


然后一旦这个概念正在检查与 is_default_constructible 不同的东西,LWG 3338 就重命名了它。因为,不同的东西应该有不同的名字。