在回答这个问题时: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
构造函数。
为什么这种行为差异的原因是什么?
答案 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&)
也是可行的,但出于其他原因,我们更喜欢移动构造函数到复制构造函数。