std :: initializer_list作为函数参数

fre*_*low 35 c++ initializer-list c++11

出于某种原因,我认为C++ 0x允许std::initializer_list作为函数的函数参数,例如,期望可以从这样构造的类型std::vector.但显然,它不起作用.这只是我的编译器,还是永远不会起作用?是因为潜在的重载解决问题吗?

#include <string>
#include <vector>

void function(std::vector<std::string> vec)
{
}

int main()
{
    // ok
    std::vector<std::string> vec {"hello", "world", "test"};

    // error: could not convert '{"hello", "world", "test"}' to 'std::vector...'
    function( {"hello", "world", "test"} );
}
Run Code Online (Sandbox Code Playgroud)

Joh*_*itb 55

GCC有一个错误.标准使这个有效.看到:

请注意,这有两个方面

  • 一般如何进行初始化?
  • 如何在重载解析期间使用初始化,它有多少成本?

第一个问题在部分回答8.5.第二个问题在部分回答13.3.例如,引用绑定在8.5.3和处理13.3.3.1.4,而列表初始化在8.5.4和中处理13.3.3.1.5.

8.5/14,16:

在表单中发生的初始化

T x = a;
Run Code Online (Sandbox Code Playgroud)

以及参数传递,函数返回,抛出异常(15.1),处理异常(15.3)和聚合成员初始化(8.5.1)称为复制初始化.
.
.
初始化器的语义如下[...]:如果初始化器是braced-init-list,则对象是列表初始化的(8.5.4).

在考虑候选者时function,编译器将看到一个初始化列表(它还没有类型 - 它只是一个语法结构!)作为参数,而a std::vector<std::string>作为参数function.为了弄清楚转换的成本是什么,以及我们是否可以在超载的情况下转换它们,13.3.3.1/5

13.3.3.1.5/1:

当参数是初始化列表(8.5.4)时,它不是表达式,并且特殊规则适用于将其转换为参数类型.

13.3.3.1.5/3:

否则,如果参数是非聚合类X并且每个13.3.1.7的重载决策选择X的单个最佳构造函数来从参数初始化列表中执行类型X的对象的初始化,则隐式转换序列是用户 - 定义转换序列.除了13.3.3.1中所述之外,允许用户定义的转换将初始化列表元素转换为构造函数参数类型.

非聚合类Xstd::vector<std::string>,我将找出下面的单个最佳构造函数.最后一条规则允许我们在以下情况下使用用户定义的转换:

struct A { A(std::string); A(A const&); };
void f(A);
int main() { f({"hello"}); }
Run Code Online (Sandbox Code Playgroud)

我们被允许将字符串文字转换为std::string,即使这需要用户定义的转换.但是,它指出了另一段的限制.怎么13.3.3.1说?

13.3.3.1/4,这是负责禁止多个用户定义的转换的段落.我们只会查看列表初始化:

但是,在考虑用户定义的转换函数[(或构造函数)]的参数时,当将初始化列表作为单个参数传递或初始化列表只有一个元素时,它是[...] 13.3.1.7的候选者并且转换为某个类X或引用(可能是cv-quali fi ed)X被认为是X的构造函数的第一个参数,或者[...],只允许标准转换序列和省略号转换序列.

请注意,这是一个重要的限制:如果不是这样,上面的内容可以使用copy-constructor建立一个同样好的转换序列,初始化将是不明确的.(注意该规则中"A或B和C"的潜在混淆:它意味着说"(A或B)和C" - 因此我们在尝试通过具有参数的X的构造函数进行转换时受到限制.类型X).

我们被委托13.3.1.7收集可用于进行此转换的构造函数.让我们从一般方面接近本段,从8.5授权我们开始8.5.4:

8.5.4/1:

列表初始化可以在直接初始化或复制初始化上下文中进行; 直接初始化上下文中的列表初始化称为直接列表初始化,复制初始化上下文中的列表初始化称为复制列表初始化.

8.5.4/2:

构造函数是初始化列表构造函数,如果它的第一个参数类型std::initializer_list<E>或引用可能std::initializer_list<E>是某些类型E的cv-quali ,并且没有其他参数或者所有其他参数都有默认参数(8.3.6).

8.5.4/3:

类型T的对象或引用的列表初始化定义如下:[...]否则,如果T是类类型,则考虑构造函数.如果T有一个初始化列表构造函数,则参数列表由初始化列表作为单个参数组成; 否则,参数列表由初始化列表的元素组成.列举了适用的构造函数(13.3.1.7),并通过重载解析(13.3)选择最佳构造函数.

这时,T是班级类型std::vector<std::string>.我们有一个参数(它还没有类型!我们只是在具有语法初始化列表的上下文中).构造函数列举如下13.3.1.7:

[...]如果T有一个初始化列表构造函数(8.5.4),则参数列表由初始化列表作为单个参数组成; 否则,参数列表由初始化列表的元素组成.对于复制列表初始化,候选函数是T的所有构造函数.但是,如果选择了显式构造函数,则初始化是错误的.

我们只会将初始化列表std::vector视为唯一的候选者,因为我们已经知道其他人不会赢得反对或者不符合论证.它有以下签名:

vector(initializer_list<std::string>, const Allocator& = Allocator());
Run Code Online (Sandbox Code Playgroud)

现在,将初始化列表转换为std::initializer_list<T>(以对参数/参数转换的成本进行分类)的规则列举在13.3.3.1.5:

当参数是初始化列表(8.5.4)时,它不是表达式,并且特殊规则适用于将其转换为参数类型.[...]如果参数类型是std::initializer_list<X>并且初始化列表的所有元素都可以隐式转换为X,则隐式转换序列是将列表元素转换为X所需的最差转换.此转换可以是用户-甚至在调用初始化列表构造函数的上下文中也定义了转换.

现在,初始化列表将成功转换,转换序列是用户定义的转换(从char const[N]std::string).如何做到这一点8.5.4再详细说明:

否则,如果T是特化std::initializer_list<E>,则初始化器列表对象的构造如下所述,并用于根据相同类型的类初始化对象的规则来初始化对象(8.5).(......)

看看8.5.4/4这最后一步是怎么做的:)

  • @Johannes:P!很好!你有没有记住"标准"?:-) (12认同)
  • Stroustrup先生刚刚通过电子邮件确认这应该有效:) (12认同)