38

出于某种原因,我认为 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"} );
}
4

4 回答 4

59

GCC 有一个错误。该标准使这一点有效。看:

请注意,这有两个方面

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

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

8.5/14,16

表单中发生的初始化

T x = a;

以及在参数传递、函数返回、抛出异常 (15.1)、处理异常 (15.3) 和聚合成员初始化 (8.5.1) 中称为复制初始化。
.
.
初始化器的语义如下[...]:如果初始化器是一个花括号初始化列表,则该对象是列表初始化的(8.5.4)。

在考虑候选function者时,编译器将看到一个初始化列表(它还没有类型——它只是一个语法结构!)作为参数,而 astd::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"}); }

我们可以将字符串文字转换为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委托我们的一般方面开始处理本段8.5.4

8.5.4/1

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

8.5.4/2

一个构造函数是一个初始化列表构造函数,如果它的第一个参数是类型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这最后一步是如何完成的:)

于 2010-03-01T17:25:10.337 回答
3

它似乎以这种方式工作:

function( {std::string("hello"), std::string("world"), std::string("test")} );

也许这是一个编译器错误,但也许您要求的隐式转换太多。

于 2010-03-01T18:15:57.480 回答
2

副手,我不确定,但我怀疑这里发生的事情是转换为 initializer_list 是一种转换,而将其转换为向量是另一种转换。如果是这种情况,您就超出了只有一次隐式转换的限制......

于 2010-03-01T17:02:55.397 回答
1

这要么是编译器错误,要么是您的编译器不支持 std::initializer_list。在 GCC 4.5.1 上测试,它编译得很好。

于 2011-08-13T12:52:21.063 回答