用双花括号初始化向量:std::string vs int
Vector initialization with double curly braces: std::string vs int
在这个问题的回答中:
表明
vector<string> v = {{"a", "b"}};
将使用 initializer_list
和 一个元素 调用 std::vector
构造函数。因此向量中的第一个(也是唯一一个)元素将从 {"a", "b"}
构造。这会导致未定义的行为,但这超出了这里的重点。
我发现的是
std::vector<int> v = {{2, 3}};
将使用 initializer_list
个 两个元素 .
调用 std::vector
构造函数
造成这种行为差异的原因是什么?
行为上的差异是由于默认参数造成的。 std::vector
有这个客户:
vector( std::initializer_list<T> init,
const Allocator& alloc = Allocator() );
注意第二个参数。 {2, 3}
推导为 std::initializer_list<int>
(不需要初始化器的转换)并且分配器是默认的。
class类型列表初始化的规则基本上是:首先,只考虑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> )
,再次。这次不是来自正常的列表初始化世界,而是直接从相同的 {2, 3}
初始化列表中直接初始化 initializer_list
,出于同样的原因,这是可行的。
要选择哪个构造函数,我们必须进入 [over.ics.list], where the vector(vector&& )
constructor is a user-defined conversion sequence but the vector(initializer_list<int> )
constructor is identity,所以它是首选。
为了完整起见,vector(vector const&)
也是可行的,但出于其他原因,我们更喜欢移动构造函数而不是复制构造函数。
在这个问题的回答中:
表明
vector<string> v = {{"a", "b"}};
将使用 initializer_list
和 一个元素 调用 std::vector
构造函数。因此向量中的第一个(也是唯一一个)元素将从 {"a", "b"}
构造。这会导致未定义的行为,但这超出了这里的重点。
我发现的是
std::vector<int> v = {{2, 3}};
将使用 initializer_list
个 两个元素 .
std::vector
构造函数
造成这种行为差异的原因是什么?
行为上的差异是由于默认参数造成的。 std::vector
有这个客户:
vector( std::initializer_list<T> init,
const Allocator& alloc = Allocator() );
注意第二个参数。 {2, 3}
推导为 std::initializer_list<int>
(不需要初始化器的转换)并且分配器是默认的。
class类型列表初始化的规则基本上是:首先,只考虑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}
- 我们将尝试用它来初始化 2const int
的数组,这是可行的。vector(std::initializer_list<int> )
,再次。这次不是来自正常的列表初始化世界,而是直接从相同的{2, 3}
初始化列表中直接初始化initializer_list
,出于同样的原因,这是可行的。
要选择哪个构造函数,我们必须进入 [over.ics.list], where the vector(vector&& )
constructor is a user-defined conversion sequence but the vector(initializer_list<int> )
constructor is identity,所以它是首选。
为了完整起见,vector(vector const&)
也是可行的,但出于其他原因,我们更喜欢移动构造函数而不是复制构造函数。