编辑:在我们开始之前,这个问题是关于std::initializer_list
的正确使用的而不是;它是关于当需要方便的语法时应该传递什么。感谢您继续关注这个话题。
C++11引入std::initializer_list
来定义接受支持的init列表参数的函数。
struct bar {
bar( char const * );
bar( int );
} dog( 42 );
fn foo( std::initializer_list< bar > args );
foo( { "blah", 3, dog } );
语法很好,但由于各种问题,它令人反感:
它们不能被有意义地移动。上述函数必须从列表中复制
dog
;无法将其转换为移动构造或删除。完全不能使用仅移动类型。(好吧,const_cast
实际上是一个有效的解决方法。如果有关于这样做的文章,我想看看。)也没有
constexpr
语义。(这将在C++1y中发布。不过这只是一个小问题。)CCD_ 6不像在其它地方那样传播;CCD_ 7从来不是CCD_。(因为它不拥有自己的内容,所以它不能对副本进行写访问,尽管将其复制到任何地方都不太安全。)
initializer_list
对象不拥有其存储(yikes);它与提供存储的完全分离的裸阵列(yikes)的关系被模糊地定义为(yike)引用与绑定的临时(四重yikes)之间的关系。
我相信这些事情会在适当的时候得到解决,但目前有没有一种最佳实践可以在不硬编码initializer_list
的情况下获得优势?有没有关于直接依赖它的文献或分析?
显而易见的解决方案是传递一个标准容器(如std::vector
)的值。一旦对象从initializer_list
复制到其中,它就会被移动构造为按值传递,然后您就可以将内容移出。一个改进是在堆栈上提供存储。一个好的库可能能够提供initializer_list
、array
和vector
的大部分优点,甚至不使用前者。
有资源吗?
它是关于当需要方便的语法时应该传递什么。
如果您想要方便的大小(即:用户只需键入一个没有函数调用或单词的{}
列表),则必须接受正确initializer_list
的所有功能和限制。即使您试图将它转换为其他东西,比如某种形式的array_ref
,您仍然必须在它们之间有一个中间initializer_list
。这意味着你无法解决你遇到的任何问题,比如无法摆脱它们。
如果它通过initializer_list
,那么您必须接受这些限制。因此,另一种选择是不使用initializer_list
,这意味着您将不得不接受某种形式的具有特定语义的容器。替代类型必须是一个聚合,这样替代对象的构造就不会遇到同样的问题。
因此,您可能正在考虑强制用户创建一个std::array
(或一个语言数组)并传递它。您的函数可以采用某种形式的array_ref
类,它可以由任意大小的任何数组构造,因此消耗函数不限于一个大小。
然而,你失去了尺寸的便利性:
foo( { "blah", 3, dog } );
与。
foo( std::array<bar, 3>{ "blah", 3, dog } );
避免这里冗长的唯一方法是让foo
将std::array
作为参数。这意味着它只能采用特定固定大小的数组。您不能使用C++14提出的dynarray
,因为它将使用initializer_list
中介。
最终,您不应该使用统一的初始化语法来传递值列表。它用于初始化对象,而不是传递事物列表。std::initializer_list
是一个类,其唯一目的是用于从相同类型的任意长的值列表中初始化特定对象。它在语言构造(一个支撑的init列表)和这些值要输入的构造函数之间充当中介对象。它允许编译器知道在给定匹配的支持init值列表时调用特定的构造函数(initializer_list
构造函数)。
这就是类存在的全部原因。
因此,您应该将该类专门用于它的设计目的。该类的存在是为了将构造函数标记为从支撑的init列表中获取值列表。因此,您应该仅将其用于接受此类值的构造函数。
如果您有一个函数foo
,它充当某些内部类型(您不想直接公开)和用户提供的值列表之间的中介,那么您需要将其他内容作为foo
的参数。具有您想要的语义的东西,然后您可以将其输入到您的内部类型中。
此外,你似乎对initializer_list
和动作有一个误解。您不能将从initializer_list
中移出,但您可以将<em]移动到>中:
foo( { "blah", 3, std::move(dog) } );
内部dog
数组中的第三个条目将被移动构造。