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 更简单和更灵活。毕竟,这就是它的设计目的..