std::initializer_list和c数组[]的优缺点是什么?
What are pros and cons of std::initializer_list and c array []?
假设我有一些假设结构:
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
- 编译器必须知道 内部类型 .
这两个版本都有缺点吗?
更新:
同样值得关注的是,您是否能够 use/convert 将一种类型转换为另一种类型 - 例如。构建标准容器时 ?
正如评论中所写,这是一个广泛的论点。
总之,我给大家提个醒。
第一种情况
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-style 数组类型的一部分这一事实在元编程中可能有点优势。
假设你想要一个return数组i
值之和的函数,你可以用C-style数组写
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()
的参数是 not-constexpr 值时,此函数也会编译。
如果你想用 std::initializer_list
写一些类似的东西会稍微复杂一些,因为列表的 size()
不一定是 compile-time 已知值,所以或者你传递它作为模板参数(但该函数不适用于 run-time 列表)或者您在函数内部使用 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
值时,该函数可以计算编译时间。
Also of concern is if you will be able to use/convert the one type to the other type - eg. when constructing standard containers ?
将数组转换为初始化列表,它很容易与 compile-time 和 run-time 已知值
一起使用
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-style 数组比较困难,因为数组的类型取决于元素的数量,因此您需要知道 compile-time 初始化列表中的元素数量,因为您不能随机访问初始化列表,更糟糕的是,因为您不能编写 return 一个 C-style 数组的函数。
我可以想象一个解决方案如下,将初始化列表转换为 std::array
(题外话建议:尽可能使用 std::array
,而不是 C-style 数组)
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
值。
数组可以是non-const。 initializer_list
只允许对元素进行常量访问。在您的示例中,您使用了 constexpr,因此隐含了 const,因此在那种情况下这并不重要。但是,如果您需要 non-const,则 initializer_list
不是一个选项。这在 initializer_list
构造函数中特别烦人,您想要将元素从列表中移出,但不能因为对象是常量。
C 风格的数组可以衰减为指向第一个元素的指针,初学者偶尔会感到困惑。 initializer_list
没有。您可以改用数组包装器,它也不会衰减,但您需要指定类型和大小,或者使用大括号初始化列表中的类型以允许模板推导:
constexpr std::array x_std_array{X{5, 6.3}};
而且,正如 max66 的回答中更深入探讨的那样,initializer_list
可以是任意大小,而数组的大小取决于它的类型,这要么是优势,要么是劣势。类型的大小部分在模板元编程中是一个优势,而 "hidden" 大小是一个优势,因为您首先不需要模板。
简单明了:initializer_list
不是容器。它是对外部分配元素的不可变视图。它完全不适合容器有用的任何场景——考虑不必要的间接(不可调整大小)、不变性、它的名字的惯用语。最重要的是,它没有合适的界面。
序列的构造函数参数似乎两者都足够的情况。如果长度是固定的(或 template-parametrized),那么 int const (&arr)[N]
是可能的,尽管 initializer_list
更简单和更灵活。毕竟,这就是它的设计目的..
假设我有一些假设结构:
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
- 编译器必须知道 内部类型 .
这两个版本都有缺点吗?
更新:
同样值得关注的是,您是否能够 use/convert 将一种类型转换为另一种类型 - 例如。构建标准容器时 ?
正如评论中所写,这是一个广泛的论点。
总之,我给大家提个醒。
第一种情况
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-style 数组类型的一部分这一事实在元编程中可能有点优势。
假设你想要一个return数组i
值之和的函数,你可以用C-style数组写
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()
的参数是 not-constexpr 值时,此函数也会编译。
如果你想用 std::initializer_list
写一些类似的东西会稍微复杂一些,因为列表的 size()
不一定是 compile-time 已知值,所以或者你传递它作为模板参数(但该函数不适用于 run-time 列表)或者您在函数内部使用 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
值时,该函数可以计算编译时间。
Also of concern is if you will be able to use/convert the one type to the other type - eg. when constructing standard containers ?
将数组转换为初始化列表,它很容易与 compile-time 和 run-time 已知值
一起使用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-style 数组比较困难,因为数组的类型取决于元素的数量,因此您需要知道 compile-time 初始化列表中的元素数量,因为您不能随机访问初始化列表,更糟糕的是,因为您不能编写 return 一个 C-style 数组的函数。
我可以想象一个解决方案如下,将初始化列表转换为 std::array
(题外话建议:尽可能使用 std::array
,而不是 C-style 数组)
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
值。
数组可以是non-const。 initializer_list
只允许对元素进行常量访问。在您的示例中,您使用了 constexpr,因此隐含了 const,因此在那种情况下这并不重要。但是,如果您需要 non-const,则 initializer_list
不是一个选项。这在 initializer_list
构造函数中特别烦人,您想要将元素从列表中移出,但不能因为对象是常量。
C 风格的数组可以衰减为指向第一个元素的指针,初学者偶尔会感到困惑。 initializer_list
没有。您可以改用数组包装器,它也不会衰减,但您需要指定类型和大小,或者使用大括号初始化列表中的类型以允许模板推导:
constexpr std::array x_std_array{X{5, 6.3}};
而且,正如 max66 的回答中更深入探讨的那样,initializer_list
可以是任意大小,而数组的大小取决于它的类型,这要么是优势,要么是劣势。类型的大小部分在模板元编程中是一个优势,而 "hidden" 大小是一个优势,因为您首先不需要模板。
简单明了:initializer_list
不是容器。它是对外部分配元素的不可变视图。它完全不适合容器有用的任何场景——考虑不必要的间接(不可调整大小)、不变性、它的名字的惯用语。最重要的是,它没有合适的界面。
序列的构造函数参数似乎两者都足够的情况。如果长度是固定的(或 template-parametrized),那么 int const (&arr)[N]
是可能的,尽管 initializer_list
更简单和更灵活。毕竟,这就是它的设计目的..