为什么std :: initializer_list不是内置语言?


96

为什么没有std::initializer_list内置核心语言?

在我看来,它是C ++ 11的重要功能,但它没有自己的reserved关键字(或类似名称)。

相反,initializer_list只是标准库中的模板类,它具有由编译器处理的新的braced-init-list语法的特殊隐式映射 {...}

乍一看,这种解决方案是很棘手的

现在是通过新的C ++语言实现方式吗:通过某些模板类的隐式角色而不是核心语言?


请考虑以下示例:

   widget<int> w = {1,2,3}; //this is how we want to use a class

为什么选择新班级:

   widget( std::initializer_list<T> init )

而不是使用类似于以下任何想法的东西:

   widget( T[] init, int length )  // (1)
   widget( T... init )             // (2)
   widget( std::vector<T> init )   // (3)
  1. 一个经典的数组,您可能会在const这里和那里添加
  2. 语言中已经存在三个点(var-args,现在是可变参数模板),为什么不重用语法(并使其内置
  3. 只是一个现有的容器,可以添加const&

所有这些已经是语言的一部分。我只写了第三个想法,我相信还有很多其他方法。


26
标准委员会讨厌添加新的关键字!
Alex Chamberlain

11
据我了解,但是如何扩展语言有很多可能性(关键字只是一个例子
emesx

10
std::array<T>不再是“语言的一部分” std::initializer_list<T>。这些几乎不是该语言所依赖的唯一库组件。请参见new/ deletetype_info各种异常类型等size_t,等等
bames53 2013年

6
@Elmes:我建议这样做const T(*)[N],因为它的行为与std::initializer_list工作方式非常相似。
Mooing Duck 2013年

1
回答了为什么std::array静态大小的数组是不太理想的选择。
boycy

Answers:


48

已经有“核心”语言功能的示例,这些功能返回std名称空间中定义的类型。typeid返回,std::type_info并且(可能是拉伸点)sizeof返回std::size_t

在前一种情况下,您已经需要包括一个标准标头,才能使用这种所谓的“核心语言”功能。

现在,对于初始化列表,碰巧不需要关键字来生成对象,语法是上下文相关的花括号。除此之外,它与相同type_info。就我个人而言,我认为没有关键字会使其变得“更hacky”。也许稍微令人惊讶,但请记住,目标是允许使用与聚合已允许的相同的括号初始化语法。

所以是的,您将来可能会期望更多的设计原则:

  • 如果出现更多可能引入新功能而无需新关键字的情况,则委员会将采用它们。
  • 如果新功能需要复杂的类型,那么这些类型将被放置std而不是内置。

因此:

  • 如果新功能需要复杂的类型并且可以在不引入新关键字的情况下引入,那么您将在这里获得所拥有的内容,即“核心语言”语法,没有新关键字,并且使用来自的库类型std

我认为,结果是在C ++中“核心语言”和标准库之间没有绝对的划分。它们是标准中的不同章节,但彼此之间都相互引用,而且一直如此。

C ++ 11中还有另一种方法,即lambda引入具有由编译器生成的匿名类型的对象。因为它们没有名称,所以它们根本不在命名空间中,当然也没有std。但是,对于初始化列表来说,这不是一种合适的方法,因为在编写接受一个初始化变量的构造函数时会使用类型名称。


1
在我看来,由于类型的这种隐式角色,这种划分是不可能的(mailny?)。type_info并且size_t是很好的参数.. size_t只是typedef ..所以我们跳过这一点。type_info和之间的区别在于initializer_list,第一个是显式运算符的结果,第二个是隐式编译器操作的结果。在我看来,initializer_list 可以用一些已经存在的容器替换它。.或者甚至更好:任何用户声明为参数类型!
emesx

4
...或者这可能是很简单的原因,如果您为此编写了一个构造函数vector,需要一个,array那么您可以从任何类型正确的数组构造一个向量,而不仅仅是由初始化列表语法生成的数组。我不确定用any构造容器是否是一件坏事array,但这并不是委员会引入新语法的意图。
史蒂夫·杰索普

2
@Christian:不,std::array甚至没有任何构造函数。这种std::array情况只是聚合初始化。另外,我欢迎您加入我在Lounge <C ++>聊天室中的讨论,因为这个讨论持续了很长时间。
Xeo

3
@ChristianRau:Xeo表示在构造初始值设定项列表时复制元素。复制初始化程序列表不会复制包含的元素。
Mooing Duck

2
@Christian List-initialisation并不意味着initializer_list。可能有很多事情,包括良好的直接初始化或聚合初始化。这些都没有涉及initializer_list(有些根本不能那样工作)。
R. Martinho Fernandes

42

C ++标准委员会似乎不希望添加新的关键字,可能是因为这增加了破坏现有代码的风险(旧版代码可以将该关键字用作变量,类或其他名称)。

而且,在我看来,定义std::initializer_list为模板容器是一个不错的选择:如果它是一个关键字,您将如何访问其基础类型?您将如何迭代呢?您还需要一堆新的运算符,这将迫使您记住更多的名称和关键字来完成与标准容器相同的操作。

像对待std::initializer_list其他任何容器一样,您就有机会编写可用于所有这些东西的通用代码。

更新:

那么为什么要引入一种新的类型,而不是使用现有的某种组合?(从评论)

首先,所有其他容器都具有用于添加,删除和包含元素的方法,这对于编译器生成的集合是不希望的。唯一的例外是std::array<>,它包装了固定大小的C样式数组,因此将成为唯一合理的候选对象。

但是,正如Nicol Bolas在评论中正确指出的那样,std::initializer_list与所有其他标准容器(包括std::array<>)之间的另一个根本区别是后者具有值语义,而std::initializer_list具有参考语义。复制的std::initializer_list,例如,不会导致它所包含的元素的副本。

此外(再次,由Nicol Bolas提供),具有用于花括号初始化列表的特殊容器,可以使用户执行初始化的方式过载。


4
那么为什么要引入一种新的类型,而不是使用现有的某种组合?
emesx

3
@elmes:其实更像是std::array。但是std::arraystd::initializaer_list包装编译时数组的同时分配内存。可以把它看成之间的差异char s[] = "array";char *s = "initializer_list";
rodrigo

2
成为普通类型会导致重载,模板专门化,名称修饰等问题。
rodrigo

2
@rodrigo:std::array不分配任何内存,这是简单的T arr[N];,与backing相同std::initializer_list
Xeo 2013年

6
@Xeo:T arr[N] 不分配内存,也许不是在动态堆但在其他地方......所以呢std::array。但是initializer_list,用户不能构造非空,因此显然不能分配内存。
rodrigo

6

这不是什么新鲜事。例如,for (i : some_container)依赖于类中特定方法或独立函数的存在some_container。C#甚至更依赖于.NET库。实际上,我认为这是一个非常优雅的解决方案,因为您可以使您的类与某些语言结构兼容,而无需复杂的语言规范。


2
独立方法beginend方法。这与IMO有所不同。
emesx

3
是吗?同样,您有一个纯粹的语言构造,它依赖于代码的特定构造。也可以通过引入新关键字来完成此操作,例如iterable class MyClass { };
Spook

但是您可以将方法放在任何需要的地方,但是可以根据需要实现它们。我有一些相似之处!这个问题是关于initializer_list,虽然
emesx

4

这确实不是什么新鲜事,有许多人指出,这种做法在C ++中就存在,例如在C#中。

尽管如此,Andrei Alexandrescu还是提到了一个很好的观点:您可能将其视为虚构的“核心”命名空间的一部分,那么它将更有意义。

因此,它实际上是这样的:core::initializer_listcore::size_tcore::begin()core::end()等等。不幸的是,std名称空间内部有一些核心语言构造,这是一个不幸的巧合。


2

它不仅可以完全在标准库中工作。包含在标准库中并不意味着编译器不能发挥巧妙的技巧。

尽管不一定在所有情况下都可以,但可以说得很清楚:此类型是众所周知的或简单的类型,可以忽略,initializer_list而只拥有初始化值应为的存储映像。

换句话说int i {5};可以等同于int i(5);或者int i=5;甚至intwrapper iw {5};如果intwrapper是一个简单的包装类在一个int与一个简单的构造服用initializer_list


我们是否有可重现的编译器示例实际上在玩这样的“聪明把戏”?在as-if下可以说是合理的,但是我想证明它的真实性。
underscore_d

优化编译器的想法是,编译器可以将代码转换为任何等效代码。C ++特别依赖于“免费”抽象的优化。从标准库替换代码的想法很普遍(请参阅gcc内置列表gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)。
Paul de Vrieze '16

实际上,您int i {5}涉及任何想法的想法std::initializer_list都是错误的。int没有接受的构造函数std::initializer_list,因此5仅用于直接构造。所以主要的例子是无关紧要的。根本就没有优化要完成。除此之外,由于std::initializer_list涉及到编译器创建和代理“虚构”数组,因此我认为它可能有助于优化,但这是编译器中的“魔术”部分,因此,与优化器是否可以用漂亮的东西做任何巧妙的事情分开包含2个导致结果的迭代器的钝对象
underscore_d

1

它不是核心语言的一部分,因为它可以完全在库中实现,仅行operator new和即可operator delete。使编译器构建起来更加复杂会有什么优势?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.