std :: initializer_list变种

fre*_*low 5 c++ syntax initialization initializer-list c++11

以下三个初始化与std::initializer_lists 之间有什么区别?

std::vector<int> a{ 2, 3, 5, 7};
std::vector<int> b( { 2, 3, 5, 7} );
std::vector<int> c = { 2, 3, 5, 7};
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,std::vector只是一个占位符,但我对一般答案感兴趣.

Joh*_*itb 2

让我们抽象一下std::vector。并称之T

T t{a, b, c};
T t = { a, b, c };
T t({a, b, c});
Run Code Online (Sandbox Code Playgroud)

前两种形式是列表初始化(它们之间唯一的区别是如果T是一个类,则explicit禁止调用第二个构造函数。如果调用其中一个,则程序将变得格式错误)。最后一种形式只是普通的直接初始化,正如我们从 C++03 中知道的那样:

T t(arg);
Run Code Online (Sandbox Code Playgroud)

出现{a, b, c}as arg意味着构造函数调用的参数是大括号初始值设定项列表。第三种形式没有列表初始化所具有的特殊处理。即使大括号初始化列表只有 1 个参数,那里也T 必须是类类型。我很高兴在这种情况下我们在发布 C++11 之前制定了明确的规则。


至于第三个调用什么构造函数,让我们假设

struct T {
  T(int);
  T(std::initializer_list<int>);
};

T t({1});
Run Code Online (Sandbox Code Playgroud)

由于直接初始化只是对重载构造函数的调用,因此我们可以将其转换为

void ctor(int); 
void ctor(std::initializer_list<int>);
void ctor(T const&);
void ctor(T &&);
Run Code Online (Sandbox Code Playgroud)

我们可以使用这两个尾随函数,但如果我们选择这些函数,我们将需要用户定义的转换。为了初始化T ref参数,将使用列表初始化,因为这不是带括号的直接初始化(因此参数初始化相当于T ref t = { 1 })。前两个函数是完全匹配的。然而,标准表示,在这种情况下,当一个函数转换为 ,std::initializer_list<T>而另一个函数没有转换时,则前一个函数获胜。ctor因此在这种情况下,将使用第二个。请注意,在这种情况下,我们不会仅使用第一个初始化器列表向量进行两阶段重载决策 - 只有列表初始化才会这样做


对于前两个,我们将使用列表初始化,它将执行上下文相关的操作。如果T是一个数组,它将初始化一个数组。以这个类为例

struct T {
  T(long);
  T(std::initializer_list<int>);
};

T t = { 1L };
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们进行两阶段过载解决。我们首先只考虑初始化列表构造函数,并查看是否有一个匹配,作为参数,我们采用整个大括号初始化列表。第二个 ctor 匹配,所以我们选择它。我们将忽略第一个构造函数。如果我们没有初始值设定项列表构造函数或者没有匹配项,我们将采用所有构造函数和初始值设定项列表的元素

struct T {
  T(long);

  template<typename A = std::initializer_list<int>>
  T(A);
};

T t = { 1L };
Run Code Online (Sandbox Code Playgroud)

在本例中,我们选择第一个构造函数,因为1L无法转换为std::initializer_list<int>.