Suppose I have some hypothetic struct:
struct X {
int i;
double d;
}
I may then write
constexpr X x_c_array[]{{5, 6.3}};
or
constexpr std::initializer_list<X> x_ilist{{5, 6.3}};
Using auto
is not possible - the compiler must know the inner type.
Are there any downsides to either version ?
Update:
Also of concern is if you will be able to use/convert the one type to the other type - eg. when constructing standard containers ?
Plain and simple: initializer_list
isn't a container. It's an immutable view onto externally allocated elements. It's utterly unsuitable for any scenario a container would be useful in—consider the needless indirection (no resizability), the immutability, the idiomacy of its name. On top of that, it has no proper interface.
A situation where both seem adequate is a constructor parameter for a sequence. If the length is fixed (or template-parametrized), then int const (&arr)[N]
is possible, although initializer_list
is far simpler and more flexible. After all, that's what it was designed and intended for..
As written in comments, it's a broad argument.
Anyway, I point your attention regarding a point.
In first case
X x1[] {{5, 6.3}};
the number of element of x1
is part of the x1
type.
So you have that
X x1[] {{5, 6.3}};
X x2[] {{5, 6.3}, {7, 8.1}};
static_assert( false == std::is_same<decltype(x1), decltype(x2)>::value );
Using an initializer list
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 );
the type remain the same changing the number of elements.
According to your needs this can be an advantage for first or second solution.
The fact that the number of elements is part of the type for C-style arrays can be a little advantage in meta programming.
Suppose you want a function that return the sum of the i
values of the arrays, with C-style array you can write
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>{}); }
and this function compile also when the argument of sum_i()
is a not-constexpr value.
If you wan to write something similar with std::initializer_list
is a little more complicated because the size()
of the list isn't necessarily a compile-time known value so or you pass it as template parameter (but the function doesn't works with run-time lists) or you use size()
inside the function, but you can't use it to initialize a std::index_sequence
.
Anyway, with initializer list, you can use the good old for()
cycle
constexpr auto sum_i (std::initializer_list<X> const lx)
{
int ret { 0 };
for ( auto const & x : lx )
ret += x.i;
return ret;
}
and the function can compute compile time when lx
is a constexpr
value.
Also of concern is if you will be able to use/convert the one type to the other type - eg. when constructing standard containers ?
Convert an array to an initializer list it's easy an works with both compile-time and run-time known value
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);
Converting an initializer list to a C-style array is more difficult because the type of the array depends from the number of elements so you need to know compile-time the number of elements in the initializer list, because you can't randomly access to an initializer list and, worse, because you can't write a function that return a C-style array.
I can imagine a solution as follows that convert an initializer list to a std::array
(off topic suggestion: use std::array
, instead of a C-style array, when possible)
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);
but x6
now is a std::array<X, 2>
, not a X[2]
, and x4
must be a constexpr
value.
An array can be non-const. initializer_list
only allows const access to the elements. In your example you use constexpr and therefore implicitly const, so in that case this doesn't matter. But if you need non-const, the initializer_list
is not an option. This is particularly annoying in initializer_list
constructors where you'd want to move the elements out from the list, but cannot because the objects are const.
The C style array can decay to pointer to first element, which beginners occasionally find confusing. initializer_list
does not. You could use an array wrapper instead which also doesn't decay, but you need to either specify the type and size, or use the type in the brace init list to allow template deduction:
constexpr std::array x_std_array{X{5, 6.3}};
And, as explored more in depth in max66's answer, initializer_list
can be of any size, while the size of an array depends on its type, which is either an advantage or a disadvantage. Size part of the type is an advantage in template meta programming, while "hidden" size is an advantage since you don't need a template in the first place.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With