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

时间:2017-10-10 11:43:42

标签: c++ c++14 initializer-list overload-resolution

在回答这个问题时:Initializing vector<string> with double curly braces

显示

vector<string> v = {{"a", "b"}};

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

我发现的是

std::vector<int> v = {{2, 3}};

使用{{1>} 两个元素调用std::vector构造函数。

为什么这种行为差异的原因是什么?

2 个答案:

答案 0 :(得分:2)

行为差异是由默认参数引起的。 std::vector有这个c'tor:

vector( std::initializer_list<T> init, 
        const Allocator& alloc = Allocator() );

注意第二个参数。 {2, 3}推导为std::initializer_list<int>(不需要转换初始化程序),并且分配器是默认的。

答案 1 :(得分:2)

类类型列表初始化的规则基本上是:首先,只考虑std::initializer_list构造函数进行重载解析,然后在必要时对所有构造函数执行重载解析(这是[over.match.list])。

从初始化列表初始化std::initializer_list<E>时,就好像我们从初始化列表中的 N 元素中实现了const E[N](来自[dcl.init.list]/5

对于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&& )构造函数为user-defined conversion sequence,但vector(initializer_list<int> )构造函数为identity,所以这是首选。

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