Initialization of all elements of an array to one default value in C++?

前端 未结 13 1049
礼貌的吻别
礼貌的吻别 2020-11-22 07:59

C++ Notes: Array Initialization has a nice list over initialization of arrays. I have a

int array[100] = {-1};

expecting it to be full with

相关标签:
13条回答
  • 2020-11-22 08:32

    In C++, it is also possible to use meta programming and variadic templates. The following post shows how to do it: Programmatically create static arrays at compile time in C++.

    0 讨论(0)
  • 2020-11-22 08:33

    The page you linked states

    If an explicit array size is specified, but an shorter initiliazation list is specified, the unspecified elements are set to zero.

    Speed issue: Any differences would be negligible for arrays this small. If you work with large arrays and speed is much more important than size, you can have a const array of the default values (initialized at compile time) and then memcpy them to the modifiable array.

    0 讨论(0)
  • 2020-11-22 08:34

    There is an extension to the gcc compiler which allows the syntax:

    int array[100] = { [0 ... 99] = -1 };
    

    This would set all of the elements to -1.

    This is known as "Designated Initializers" see here for further information.

    Note this isn't implemented for the gcc c++ compiler.

    0 讨论(0)
  • 2020-11-22 08:36

    Using std::array, we can do this in a fairly straightforward way in C++14. It is possible to do in C++11 only, but slightly more complicated.

    Our interface is a compile-time size and a default value.

    template<typename T>
    constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
        return std::array<std::decay_t<T>, 0>{};
    }
    
    template<std::size_t size, typename T>
    constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
        return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
    }
    
    
    template<std::size_t size, typename T>
    constexpr auto make_array_n(T && value) {
        return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
    }
    

    The third function is mainly for convenience, so the user does not have to construct a std::integral_constant<std::size_t, size> themselves, as that is a pretty wordy construction. The real work is done by one of the first two functions.

    The first overload is pretty straightforward: It constructs a std::array of size 0. There is no copying necessary, we just construct it.

    The second overload is a little trickier. It forwards along the value it got as the source, and it also constructs an instance of make_index_sequence and just calls some other implementation function. What does that function look like?

    namespace detail {
    
    template<std::size_t size, typename T, std::size_t... indexes>
    constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
        // Use the comma operator to expand the variadic pack
        // Move the last element in if possible. Order of evaluation is well-defined
        // for aggregate initialization, so there is no risk of copy-after-move
        return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
    }
    
    }   // namespace detail
    

    This constructs the first size - 1 arguments by copying the value we passed in. Here, we use our variadic parameter pack indexes just as something to expand. There are size - 1 entries in that pack (as we specified in the construction of make_index_sequence), and they have values of 0, 1, 2, 3, ..., size - 2. However, we do not care about the values (so we cast it to void, to silence any compiler warnings). Parameter pack expansion expands out our code to something like this (assuming size == 4):

    return std::array<std::decay_t<T>, 4>{ (static_cast<void>(0), value), (static_cast<void>(1), value), (static_cast<void>(2), value), std::forward<T>(value) };
    

    We use those parentheses to ensure that the variadic pack expansion ... expands what we want, and also to ensure we are using the comma operator. Without the parentheses, it would look like we are passing a bunch of arguments to our array initialization, but really, we are evaluating the index, casting it to void, ignoring that void result, and then returning value, which is copied into the array.

    The final argument, the one we call std::forward on, is a minor optimization. If someone passes in a temporary std::string and says "make an array of 5 of these", we would like to have 4 copies and 1 move, instead of 5 copies. The std::forward ensures that we do this.

    The full code, including headers and some unit tests:

    #include <array>
    #include <type_traits>
    #include <utility>
    
    namespace detail {
    
    template<std::size_t size, typename T, std::size_t... indexes>
    constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
        // Use the comma operator to expand the variadic pack
        // Move the last element in if possible. Order of evaluation is well-defined
        // for aggregate initialization, so there is no risk of copy-after-move
        return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
    }
    
    }   // namespace detail
    
    template<typename T>
    constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
        return std::array<std::decay_t<T>, 0>{};
    }
    
    template<std::size_t size, typename T>
    constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
        return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
    }
    
    template<std::size_t size, typename T>
    constexpr auto make_array_n(T && value) {
        return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
    }
    
    
    
    struct non_copyable {
        constexpr non_copyable() = default;
        constexpr non_copyable(non_copyable const &) = delete;
        constexpr non_copyable(non_copyable &&) = default;
    };
    
    int main() {
        constexpr auto array_n = make_array_n<6>(5);
        static_assert(std::is_same<std::decay_t<decltype(array_n)>::value_type, int>::value, "Incorrect type from make_array_n.");
        static_assert(array_n.size() == 6, "Incorrect size from make_array_n.");
        static_assert(array_n[3] == 5, "Incorrect values from make_array_n.");
    
        constexpr auto array_non_copyable = make_array_n<1>(non_copyable{});
        static_assert(array_non_copyable.size() == 1, "Incorrect array size of 1 for move-only types.");
    
        constexpr auto array_empty = make_array_n<0>(2);
        static_assert(array_empty.empty(), "Incorrect array size for empty array.");
    
        constexpr auto array_non_copyable_empty = make_array_n<0>(non_copyable{});
        static_assert(array_non_copyable_empty.empty(), "Incorrect array size for empty array of move-only.");
    }
    
    0 讨论(0)
  • 2020-11-22 08:37

    Using the syntax that you used,

    int array[100] = {-1};
    

    says "set the first element to -1 and the rest to 0" since all omitted elements are set to 0.

    In C++, to set them all to -1, you can use something like std::fill_n (from <algorithm>):

    std::fill_n(array, 100, -1);
    

    In portable C, you have to roll your own loop. There are compiler-extensions or you can depend on implementation-defined behavior as a shortcut if that's acceptable.

    0 讨论(0)
  • 2020-11-22 08:39

    The page you linked to already gave the answer to the first part:

    If an explicit array size is specified, but an shorter initiliazation list is specified, the unspecified elements are set to zero.

    There is no built-in way to initialize the entire array to some non-zero value.

    As for which is faster, the usual rule applies: "The method that gives the compiler the most freedom is probably faster".

    int array[100] = {0};
    

    simply tells the compiler "set these 100 ints to zero", which the compiler can optimize freely.

    for (int i = 0; i < 100; ++i){
      array[i] = 0;
    }
    

    is a lot more specific. It tells the compiler to create an iteration variable i, it tells it the order in which the elements should be initialized, and so on. Of course, the compiler is likely to optimize that away, but the point is that here you are overspecifying the problem, forcing the compiler to work harder to get to the same result.

    Finally, if you want to set the array to a non-zero value, you should (in C++, at least) use std::fill:

    std::fill(array, array+100, 42); // sets every value in the array to 42
    

    Again, you could do the same with an array, but this is more concise, and gives the compiler more freedom. You're just saying that you want the entire array filled with the value 42. You don't say anything about in which order it should be done, or anything else.

    0 讨论(0)
提交回复
热议问题