Search code examples
c++c++17constexprinitializer-list

What are pros and cons of std::initializer_list and c array []?


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 ?


Solution

  • 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.