假设我有一些假设的结构:
struct X {
int i;
double d;
}
然后我可以写
constexpr X x_c_array[]{{5, 6.3}};
或
constexpr std::initializer_list<X> x_ilist{{5, 6.3}};
不可能使用auto
-编译器必须知道内部类型。
两个版本都有缺点吗?
更新:
还值得关注的是,您是否能够使用/将一种类型转换为另一种类型-例如。在构造标准容器时?
答案 0 :(得分:5)
简单明了:initializer_list
不是容器。这是外部分配元素的不变视图。完全不适合容器将在其中使用的任何情况-考虑不必要的间接(无可调整性),不变性,其名称的惯用语。最重要的是,它没有适当的接口。
看起来很合适的情况是序列的构造函数参数。如果长度是固定的(或模板参数化),则int const (&arr)[N]
是可能的,尽管initializer_list
更简单,更灵活。毕竟,这就是它的设计意图。
答案 1 :(得分:2)
正如评论中所写,这是一个广泛的争论。
无论如何,我会指出您的关注点。
在第一种情况下
X x1[] {{5, 6.3}};
x1
的元素数是x1
类型的一部分。
所以你有
X x1[] {{5, 6.3}};
X x2[] {{5, 6.3}, {7, 8.1}};
static_assert( false == std::is_same<decltype(x1), decltype(x2)>::value );
使用初始化列表
std::initializer_list<X> x3 {{5, 6.3}};
std::initializer_list<X> x4 {{5, 6.3}, {7, 8.1}};
static_assert( true == std::is_same<decltype(x3), decltype(x4)>::value );
类型保持不变,更改元素的数量。
根据您的需求,这可能是第一个或第二个解决方案的优势。
在C语言风格的数组中,元素的数目是类型的一部分这一事实在元编程中可能有点优势。
假设您想要一个函数,该函数返回数组的i
值的和,可以编写C样式的数组
template <std::size_t N, std::size_t ... Is>
constexpr auto sum_i_helper (X const (&xArr)[N], std::index_sequence<Is...>)
{ return (... + xArr[Is].i); }
template <std::size_t N>
constexpr auto sum_i (X const (&xArr)[N])
{ return sum_i_helper(xArr, std::make_index_sequence<N>{}); }
,并且当sum_i()
的参数为非constexpr值时,此函数也会编译。
如果您想编写与std::initializer_list
类似的内容会稍微复杂一点,因为列表的size()
不一定是编译时已知的值,所以您可以将其作为模板参数传递(但该功能不适用于运行时列表),或者您在功能内部使用了size()
,但是您不能使用它来初始化std::index_sequence
。
无论如何,通过初始化列表,您可以使用旧的for()
循环
constexpr auto sum_i (std::initializer_list<X> const lx)
{
int ret { 0 };
for ( auto const & x : lx )
ret += x.i;
return ret;
}
,当lx
为constexpr
值时,该函数可以计算编译时间。
还值得关注的是,您是否能够使用/将一种类型转换为另一种类型-例如。在构造标准容器时?
将数组转换为初始值设定项列表很容易同时进行编译时和运行时的已知值
template <std::size_t N, std::size_t ... Is>
constexpr auto convertX_h (X const (&xArr)[N], std::index_sequence<Is...>)
{ return std::initializer_list<X>{ xArr[Is]... }; }
template <std::size_t N>
constexpr auto convertX (X const (&xArr)[N])
{ return convertX_h(xArr, std::make_index_sequence<N>{}); }
// ....
X x1[] {{5, 6.3}};
std::initializer_list<X> x5 = convertX(x1);
将初始值设定项列表转换为C样式的数组更加困难,因为该数组的类型取决于元素数,因此您需要了解编译时初始值设定项中的元素数列表,因为您不能随机访问初始化列表,更糟糕的是,您不能编写返回C样式数组的函数。
我可以想象出一种将初始化列表转换为std::array
的解决方案(主题建议:尽可能使用std::array
而不是C样式数组)
template <std::size_t N>
constexpr auto convertX (std::initializer_list<X> const lx)
{
std::array<X, N> ret;
std::size_t i { 0u };
for ( auto const & x : lx )
ret[i++] = x;
return ret;
}
// ...
constexpr std::initializer_list<X> x4 {{5, 6.3}, {7, 8.1}};
auto x6 = convertX<x4.size()>(x4);
但是x6
现在是std::array<X, 2>
,而不是X[2]
,并且x4
必须是constexpr
值。
答案 2 :(得分:1)
数组可以是非常量的。 initializer_list
仅允许const访问元素。在您的示例中,您使用constexpr,因此使用了隐式const,因此在这种情况下这无关紧要。但是,如果您需要非常量,则initializer_list
不是一个选择。在initializer_list
构造函数中,这尤其令人讨厌,在该构造函数中,您希望将元素从列表中移出,但不能这样做,因为对象是const。
C样式数组可能会衰减到指向第一个元素的指针,初学者有时会感到困惑。 initializer_list
没有。您可以改用不会衰减的数组包装器,但是您需要指定类型和大小,或者使用大括号初始化列表中的类型来允许模板推导:
constexpr std::array x_std_array{X{5, 6.3}};
而且,正如在max66的答案中更深入地探讨的那样,initializer_list
可以是任意大小,而数组的大小取决于其类型,这是有利还是不利。类型的大小部分在模板元编程中是一个优点,而“隐藏”大小是一个优点,因为您根本不需要模板。