std::initializer_list 作为函数参数
Posted
技术标签:
【中文标题】std::initializer_list 作为函数参数【英文标题】:std::initializer_list as function argument 【发布时间】:2011-01-22 09:10:13 【问题描述】:出于某种原因,我认为 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" );
【问题讨论】:
闻起来像 GCC 错误。特别是考虑到使用 ` = ... ` 初始化vec
工作正常。参数传递应该与= initializer
具有相同的含义。 (都是复制初始化)。
他没有使用= ... ', just
T var ... `。
@Peter:两者在 C++0x 中都一样 ;)
作为记录,它也不能在 c++0x 的 clang++ 3.0 中编译。非聚合类型 'std::vector<:string>' 不能用初始化列表初始化。
【参考方案1】:
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;
以及在参数传递中、函数返回、抛出异常 (15.1)、处理异常 (15.3) 和聚合成员初始化 (8.5.1) 称为复制初始化。 . . 初始化器的语义如下[...]:如果初始化器是花括号初始化列表,则对象是列表初始化的(8.5.4)。
在考虑候选function
时,编译器将看到一个初始化列表(它还没有类型——它只是一个语法结构!)作为参数,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 中另有说明。
非聚合类X
是std::vector<std::string>
,我将在下面找出最好的构造函数。最后一条规则允许我们在以下情况下使用用户定义的转换:
struct A A(std::string); A(A const&); ;
void f(A);
int main() f("hello");
我们可以将字符串文字转换为std::string
,即使这需要用户定义的转换。但是,它指出了另一段的限制。 13.3.3.1
说什么?
13.3.3.1/4
,这是负责禁止多个用户定义的转换的段落。我们只会看列表初始化:
但是,当考虑作为单个参数传递初始化程序列表或初始化程序列表具有 [...] 13.3.1.7 候选的用户定义转换函数 [(或构造函数)] 的参数时恰好一个元素和到某个类 X 的转换或对(可能是 cv 限定的)X 的引用被认为是 X 的构造函数的第一个参数,或者 [...],只允许标准转换序列和省略号转换序列。
请注意这是一个重要的限制:如果不是这样,上面可以使用复制构造函数来建立一个同样好的转换序列,并且初始化将是模棱两可的。 (请注意该规则中“A 或 B 和 C”的潜在混淆:它的意思是“(A 或 B)和 C” - 所以我们在尝试通过X 的构造函数,其参数类型为X
)。
我们被委托给13.3.1.7
来收集我们可以用来进行这种转换的构造函数。让我们从将我们委托给8.5.4
的8.5
开始从一般方面来处理这一段:
8.5.4/1
:
列表初始化可以发生在直接初始化或复制初始化上下文中;直接初始化上下文中的列表初始化称为direct-list-initialization,复制初始化上下文中的列表初始化称为copy-list-initialization。
8.5.4/2
:
一个构造函数是一个 initializer-list 构造函数,如果它的第一个参数是
std::initializer_list<E>
类型或对某些类型 E 的可能具有 cv 限定的std::initializer_list<E>
的引用,并且要么没有其他参数否则所有其他参数都有默认参数(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());
现在,将初始化列表转换为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>
的特化,则按如下所述构造一个 initializer_list 对象,并用于根据从相同类型的类(8.5)中初始化对象的规则来初始化该对象。 (...)
请参阅8.5.4/4
这最后一步是如何完成的 :)
【讨论】:
@Johannes:呸!很好!你记住了“标准”吗? :-) 先生。 Stroustrup 刚刚通过电子邮件确认这应该可行:) 一整天都被(A or B)和C规则弄糊涂了,直到看到这篇文章...... B)和C? @user 我认为不是。我认为要理解这句话的真正含义需要考虑两种可能的含义,并注意到一种含义(读作“A 或(B 和 C)”)是没有意义的。 @user 但我想将邮件发送到 wg21 编辑器不会有什么坏处。我们应该寻求提供最容易理解的措辞,而这样的陷阱会使我们更难理解其含义。【参考方案2】:它似乎是这样工作的:
function( std::string("hello"), std::string("world"), std::string("test") );
也许这是一个编译器错误,但也许你要求的隐式转换太多。
【讨论】:
【参考方案3】:顺便说一句,我不确定,但我怀疑这里发生的事情是转换为 initializer_list 是一种转换,而将其转换为向量是另一种转换。如果是这种情况,您就超出了仅一次隐式转换的限制...
【讨论】:
【参考方案4】:这要么是编译器错误,要么是您的编译器不支持 std::initializer_list。在 GCC 4.5.1 上测试,编译正常。
【讨论】:
以上是关于std::initializer_list 作为函数参数的主要内容,如果未能解决你的问题,请参考以下文章
为啥 `std::initializer_list` 不提供下标运算符?
为啥 std::min(std::initializer_list<T>) 按值接受参数?
为啥 std::initializer_list 不是内置语言?