【问题标题】:Why does the standard differentiate between direct-list-initialization and copy-list-initialization?为什么标准区分直接列表初始化和复制列表初始化?
【发布时间】:2012-11-07 19:14:06
【问题描述】:

我们知道T v(x);被称为direct-initialization,而T v = x;被称为copy-initialization,意思是它会构造一个临时的Tx 将被复制/移动到 v(很可能被省略)。

对于列表初始化,标准根据上下文区分两种形式。 T v{x}; 被称为 direct-list-initializationT v = {x}; 被称为 copy-list-initialization

§8.5.4 [dcl.init.list] p1

[...] 列表初始化可以发生在直接初始化或复制初始化中 上下文;直接初始化上下文中的列表初始化称为direct-list-initialization,复制初始化上下文中的列表初始化称为copy-list-initialization。 [...]

但是,在整个标准中,每个参考文献只有两个。对于直接列表初始化,在创建像 T{x} (§5.2.3/3) 这样的临时对象时会提到它。对于复制列表初始化,它用于返回语句中的表达式,如return {x}; (§6.6.3/2)。

现在,下面的sn-p呢?

#include <initializer_list>

struct X{
  X(X const&) = delete; // no copy
  X(X&&) = delete; // no move
  X(std::initializer_list<int>){} // only list-init from 'int's
};

int main(){
  X x = {42};
}

通常,从X x = expr; 模式,我们预计代码编译失败,因为X 的移动构造函数定义为deleted。但是,最新版本的 Clang 和 GCC 可以很好地编译上面的代码,并且在挖掘了一下(并找到上面的引用)之后,这似乎是正确的行为。该标准只定义了整个列表初始化的行为,除了上述几点之外,根本不区分这两种形式。好吧,至少据我所知。

所以,再次总结一下我的问题:

如果它们(显然)做完全相同的事情,那么将列表初始化分成两种形式有什么用?

【问题讨论】:

  • 如果你创建了第三个构造函数explicit,它会停止工作吗?
  • 直接初始化和复制初始化的区别在 8.5 #16 中定义。直接初始化只考虑构造函数,而复制初始化考虑用户定义的转换序列。因此,使 ctor 显式将导致编译器错误。

标签: c++ c++11 language-lawyer list-initialization


【解决方案1】:

因为他们做完全相同的事情。如 13.3.1.7 [over.match.list] 所述:

在复制列表初始化中,如果选择了显式构造函数,则初始化格式错误。

简而言之,您只能在复制列表初始化上下文中使用隐式转换。

这是明确添加的,以使统一初始化不,嗯,统一。是的,我知道这听起来很愚蠢,但请耐心等待。

2008年N2640 was published (PDF),看一下统一初始化的现状。它专门研究了直接初始化 (T{...}) 和复制初始化 (T = {...}) 之间的区别。

总而言之,担心explicit 构造函数实际上会变得毫无意义。如果我有某种类型 T 我希望能够从整数构造,但我不想隐式转换,我将构造函数标记为显式。

然后有人这样做:

T func()
{
  return {1};
}

如果没有当前的措辞,这将调用我的explicit 构造函数。那么构造函数 explicit 变化不大有什么好处呢?

以目前的措辞,你至少需要直接使用名称:

T func()
{
  return T{1};
}

【讨论】:

  • 我应该知道这一点,因为我经常抱怨 std::tuplestd::unique_ptrs 的构造函数是 explicit 所以我不能这样做 return {a, b, c};。 :)
  • 好吧,由于统一初始化并非在所有类型上都统一工作,我认为“使统一初始化不是,嗯,统一”这句话实际上是无效的。例如。在 sn-p X x; X y{x}; 中,y 是所有可复制类型的 x 的副本除了那些至少有一个来自任何类型的列表构造函数的类型(对于这些类型,它通常不会'不编译)。所以在模板中,如果你想复制或转换,你必须求助于旧的 C++03 初始化。
  • @jpalecek:这不是真的。 initializer_list 构造函数将在给定的braced-init-list 与构造函数的类型相同时使用。因此,只有当X 有一个X(initializer_list&lt;X&gt;) 构造函数时才会调用它,这不太可能。即使它这样做了,它不会只是......复制成员吗?我不确定你在这里关心的是什么。问题通常与vector&lt;int&gt; 之类的事情有关,其中采用 size_t 的构造函数与 initializer_list 构造函数发生冲突。
  • @jpalecek 我相信这是一个 gcc4.5.1 错误,因为 this 在 4.7.0 上编译。 b 正在通过(编译器生成的)复制构造函数进行初始化,并且没有选择采用 initializer_list 的构造函数,即使它是首选,因为括号初始化列表的成员不能转换为 @ 987654344@。正如 Nicol 在之前的评论中所说,如果存在 A::A(initializer_list&lt;A&gt;) 构造函数,则将在复制构造函数之上选择它。
  • @jpalecek:来自第 13.3.1.7 节,p1:-Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument. - If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list. 所以您一定是在查看旧版本的标准(我在任何地方都找不到该声明)。
猜你喜欢
  • 2018-10-29
  • 1970-01-01
  • 2014-03-16
  • 1970-01-01
  • 2010-11-06
  • 2018-07-15
相关资源
最近更新 更多