使用双花​​括号进行向量初始化:std::string 与 int

bol*_*lov 7 c++ initializer-list overload-resolution c++14

在这个问题的回答中:用双花括号初始化向量<string>

结果表明

vector<string> v = {{"a", "b"}};
Run Code Online (Sandbox Code Playgroud)

将使用一个元素std::vector调用构造函数。因此向量中的第一个(也是唯一的)元素将由 构造。这会导致未定义的行为,但这超出了这里的重点。initializer_list{"a", "b"}

我发现的是

std::vector<int> v = {{2, 3}};
Run Code Online (Sandbox Code Playgroud)

std::vector使用两个元素initializer_list中的一个调用构造函数。

为什么会出现这种行为差异的原因呢?

Bar*_*rry 5

类类型的列表初始化的规则基本上是:首先,仅考虑std::initializer_list构造函数进行重载决策,然后,如果需要,对所有构造函数进行重载决策(这是[over.match.list])。

当从初始化器列表初始化 a 时std::initializer_list<E>,就好像我们const E[N]从初始化器列表中的N个元素(来自[dcl.init.list]/5)具体化 a 。

因为vector<string> v = {{"a", "b"}};我们首先尝试initializer_list<string>构造函数,这将涉及尝试初始化一个 1 的数组const string,其中一个string是从 初始化的{"a", "b"}。这是可行的,因为 的迭代器对构造函数string,因此我们最终得到一个包含单个字符串的向量(这是 UB,因为我们违反了该字符串构造函数的先决条件)。这是最简单的情况。


因为vector<int> v = {{2, 3}};我们首先尝试initializer_list<int>构造函数,这将涉及尝试初始化一个 1 的数组const int,其中一个int是从 初始化的{2, 3}。这是不可行的

那么,我们考虑所有vector构造函数重新进行重载决策。现在,我们得到了两个可行的构造函数:

  • vector(vector&& ),因为当我们在那里递归地初始化参数时,初始化器列表将是{2, 3}- 我们将尝试用它来初始化一个 2 的数组const int,这是可行的。
  • vector(std::initializer_list<int> ), 再次。initializer_list这次不是从正常的列表初始化世界,而是从同一个初始化列表直接初始化{2, 3},出于同样的原因这是可行的。

要选择哪个构造函数,我们必须进入[over.ics.list],其中vector(vector&& )构造函数是用户定义的转换序列,但vector(initializer_list<int> )构造函数是identity,所以它是首选。


为了完整性,vector(vector const&)也是可行的,但出于其他原因,我们更喜欢移动构造函数而不是复制构造函数。