【问题标题】:What is the difference between `std::default_initializable` and `std::is_default_constructible`?`std::default_initializable` 和 `std::is_default_constructible` 有什么区别?
【发布时间】:2020-10-16 01:33:03
【问题描述】:

C++20 添加了一个名为std::default_initializable 的概念。与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; 格式良好。

【问题讨论】:

标签: c++ c++20


【解决方案1】:

这基本上是LWG 3149

DefaultConstructible<T> 等价于Constructible<T> (18.4.11 [concept.constructible]),等价于is_constructible_v<T> (20.15.4.3 [meta.unary.prop])。根据 20.15.4.3 [meta.unary.prop] 第 8 段:

模板特化is_­constructible<T, Args...>的谓词条件当且仅当以下变量定义对于某个发明的变量t是良构的:

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

DefaultConstructible&lt;T&gt; 要求 T 类型的对象可以值初始化,而不是按预期进行默认初始化

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

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 就将其重命名。因为,不同的东西应该有不同的名字。

【讨论】:

  • 通过“复制列表初始化”,您的意思是 - 尝试使用复制 ctor?我不确定那个词是什么意思。
  • 显然还有一个更微不足道的区别,is_default_constructible 不要求类型是可破坏的。
  • @einpoklum 这里有一个解释:en.cppreference.com/w/cpp/language/list_initialization
  • @einpoklum:不,他的意思是“复制列表初始化”,一种特定形式的列表初始化(又名:你用T t = {...}; 得到的)。没有给定初始化器或默认初始化器的聚合的任何成员都将是initialized by copy-list-initialization from an empty braced-init-list
  • @einpoklum: y 正在进行直接列表初始化; y.x,作为y 的子对象,必须以某种方式进行初始化。请记住:如果您为聚合成员提供初始化程序,则该成员将由该初始化程序的 copy-initialization 初始化,无论聚合是直接初始化还是复制列表初始化。所以标准只是用{} 代替你没有初始化的每个成员。因此,它们从调用复制列表初始化的{} 进行复制初始化。
【解决方案2】:

LWG 问题 3338

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

3149 DefaultConstructible 应该需要默认初始化

讨论

[...] [概念] DefaultConstructible&lt;T&gt; 要求 T 类型的对象可以进行值初始化,而不是按预期进行默认初始化

需要一个要求对象类型可以默认初始化的约束。 [...]

用户还需要一种机制来提供这种约束,他们可能会选择DefaultConstructible尽管它有些不合适

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

Tim Song 指出 {} 不一定是一个有效的初始化器 DefaultConstructible 类型。在这个示例程序中(参见编译器 探险家):

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

S1 可以默认初始化,但不能从列表初始化 空的括号初始化列表。在场的人一致认为 DefaultConstructible应该禁止这种病态的 要求初始化表单有效

Issue 3149 已被移至 WP 状态(除了作为技术勘误外,基本上已被接受)。

Issue 3338 随后也获得了 WP 状态,将 default_constructible 概念重命名为 default_initializable

3338。将 default_constructible 重命名为 default_initializable

讨论

[...] 在 LEWG 的讨论中明确表示,3149 将改变概念,要求默认初始化有效,而不是 is_default_constructible 特征所要求的值初始化。 LEWG 同意如果特征和概念名称非常相似但含义略有不同会令人困惑 [...]。

建议的解决方案:

[...] 将稳定名称“[concept.defaultconstructible]”更改为“[concept.default.init]”并将“Concept default_constructible”重命名为“Concept default_initializable”。将所有对名称 default_constructible 的引用替换为 default_initializable(出现 20 次)。

【讨论】:

【解决方案3】:

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

看着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

如果std::is_constructible&lt;T&gt;::value 为真,则提供成员常量value 等于true,否则valuefalse

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

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

既然我们只看std::constructible_from&lt;T&gt;,那么我们可以看到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; };

【讨论】:

  • std::is_default_constructible 上很好找到,不需要类型是可破坏的。似乎出于此 trait 的目的,T foo; 需要析构函数这一事实被忽略了...
  • @HolyBlackCat:特征不定义哪些语句或表达式是有效的。
  • @NicolBolas std::is_constructibleT foo(...); 的有效性方面是defined,这就是造成我困惑的原因。但随后它说此声明的各种“副作用”(例如似乎需要析构函数)被忽略了。
猜你喜欢
  • 2014-07-22
  • 2023-04-09
  • 2020-09-30
  • 2021-10-16
  • 2015-07-18
  • 2019-02-26
  • 2013-02-07
  • 2020-04-25
相关资源
最近更新 更多