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

12

344

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 -1's but its not, only first value is and the rest are 0's mixed with random values.

The code

int array[100] = {0};

works just fine and sets each element to 0.

What am I missing here.. Can't one initialize it if the value isn't zero ?

And 2: Is the default initialization (as above) faster than the usual loop through the whole array and assign a value or does it do the same thing?

Dreddy answered 30/6, 2009 at 20:10 Comment(6)
The behaviour in C and C++ is different. In C {0} is a special case for a struct initializer, however AFAIK not for arrays. int array[100]={0} should be the same as array[100]={[0]=0}, which as a side-effect will zero all other elements. A C compiler should NOT behave as you describe above, instead int array[100]={-1} should set the first element to -1 and the rest to 0 (without noise). In C if you have a struct x array[100], using ={0} as an initializer is NOT valid. You can use {{0}} which will initialize the first element and zero all others, will in most cases will be the same thing.Edwin
@FredrikWidlund It's the same in both languages. {0} is not a special case for structs nor arrays. The rule is that elements with no initializer get initialized as if they had 0 for an initializer. If there are nested aggregates (e.g. struct x array[100]) then initializers are applied to the non-aggregates in "row-major" order ; braces may optionally be omitted doing this. struct x array[100] = { 0 } is valid in C; and valid in C++ so long as the first member of struct X accepts 0 as initializer.Sop
{ 0 } is not special in C, but it's much harder to define a data type that can't be initialized with it since there are no constructors and thus no way to stop 0 from being implicitly converted and assigned to something.Vickievicksburg
Voted to reopen because the other question is about C. There are many C++ ways to initialize an array that are not valid in C.Deltadeltaic
Also voted for re-open - C and C++ are different languagesSandrocottus
I removed the C tag since this question is clearly focused on C++ and edited the question´s title accordingly. Now the question can be reopened because it is no longer a duplicate of the illustrated C question. For all of those of you who encounter this question in the future and might want to take a look at the same question for C, here is the link: How to initialize all members of an array to the same value?Eyot
B
460

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.

Bathetic answered 30/6, 2009 at 20:14 Comment(11)
That also answered an indirect question about how to fill the array with default values "easily". Thank you.Dreddy
Does this rule "set the first element to -1 and the rest to 0" also apply to C?Greco
@MattJoiner: yes, I believe so.Bathetic
Note that you will need to add #include <vector> to use this solution.Escapee
@chessofnerd: not precisely, #include <algorithm> is the right header, <vector> may or may not include it indirectly, that would depend on your implementation.Bathetic
You don't have to resort to initializing the array during runtime. If you really need the initialization to happen statically, it's possible to use variadic templates and variadic sequences to generate the desired sequence of ints and expand it into the initializer of the array.Barcus
I would prefer int array[100] = {-1, }; otherwise it might confuse people that you are trying to initialize all elements to -1.Laundrywoman
@Evan Teran I guess the correct syntax to use fill_n for 2D array is int array2D[3][2]; std::fill_n(&array2D[0][0], 6, -1);. However, VS2012 gives warning "'std::_Fill_n': Function call with parameters that may be unsafe - this call relies on the caller to check that the passed values are correct. To disable this warning, use -D_SCL_SECURE_NO_WARNINGS. See documentation on how to use Visual C++ 'Checked Iterators'"Geesey
@ontherocks, no there is no correct way to use a single call to fill_nto fill an entire 2D array. You need to loop across one dimension, while filling in the other.Bathetic
This is an answer to some other question. std::fill_n is not initialization.Abortifacient
@BenVoigt C++ lacks a succinct way to initialise arrays to anything other than the types default value, and the as-if rule means that a compiler doesn't have to do the zero-initialisationOman
L
158

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.

Levator answered 30/6, 2009 at 20:22 Comment(1)
Awesome. This syntax also seems to work in clang (so can be used on iOS/Mac OS X).Rocky
P
45

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.

Phosphorism answered 30/6, 2009 at 20:18 Comment(2)
Good answer. Note that in C++ (not in C) you can do int array[100] = {}; and give the compiler the most freedom :)Buckner
agreed, excellent answer. But for a fixed sized array, it'd use std::fill_n :-P.Bathetic
O
22

C++11 has another (imperfect) option:

std::array<int, 100> a;
a.fill(-1);
Onrush answered 11/9, 2014 at 9:20 Comment(1)
or std::fill(begin(a), end(a), -1)Selfdetermination
O
12

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.");
}
Onesided answered 3/12, 2015 at 4:53 Comment(2)
Your non_copyable type is actually copyable by the means of operator=.Balkin
@Hertz: The explicit defaulting of the move constructor causes the copy and move assignment operators to be implicitly deleted. It doesn't matter for this test case, though, since there are no assignments.Onesided
S
10

With {} you assign the elements as they are declared; the rest is initialized with 0.

If there is no = {} to initalize, the content is undefined.

Stypsis answered 30/6, 2009 at 20:13 Comment(0)
M
8

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.

Mcfarland answered 30/6, 2009 at 20:14 Comment(11)
the memcpy isn't a very good idea, since that would be comparable to just setting the values directly speed wise.Bathetic
I don't see the need for the copy and the const array: Why not create the modifiable array in the first place with the pre-filled values?Buckner
Thanks for the speed explanation and how to do it if the speed is an issue with a large array size (which is in my case)Dreddy
The initializer list is done at compile time and loaded at runtime. No need to go copying things around.Moulder
@litb, @Evan: For example gcc generates dynamic initialization (lots of movs) even with optimizations enabled. For large arrays and tight performance requirements, you want to do the init at compile time. memcpy is probably better optimized for large copies than lots of plain movs alone.Mcfarland
@Martin York: For const arrays, yes. Not for non-const arrays.Mcfarland
@laalto, if it does so, then it's a bug: The compiler, according to the C++ standard, has to apply static initialization to such PODs (int[N]) initialized entirely with constant expressions (-1, ...). I take it you mean int data[] = { 1, 2, 3 }; . Of course, i'm talking about objects having namespace-scope here.Buckner
(Though initializing with -1 is probably better done with memset(..., 0xff), assuming (int32)-1 == 0xffffffff.)Mcfarland
@laalto: that's assuming 2's compliment, which is not guaranteed.Bathetic
@litb: Ah, scope was not mentioned. I was thinking of function scope automatics, not namespace scope statics.Mcfarland
@laalto, Oh this makes sense then. After all, it needs a fresh copy each time, in case you modified the array afterwards. Silly me not asking for scope first :)Buckner
G
5

For the case of an array of single-byte elements, you can use memset to set all elements to the same value.

There's an example here.

Garofalo answered 1/7, 2009 at 7:31 Comment(0)
C
4

Another way of initializing the array to a common value, would be to actually generate the list of elements in a series of defines:

#define DUP1( X ) ( X )
#define DUP2( X ) DUP1( X ), ( X )
#define DUP3( X ) DUP2( X ), ( X )
#define DUP4( X ) DUP3( X ), ( X )
#define DUP5( X ) DUP4( X ), ( X )
.
.
#define DUP100( X ) DUP99( X ), ( X )

#define DUPx( X, N ) DUP##N( X )
#define DUP( X, N ) DUPx( X, N )

Initializing an array to a common value can easily be done:

#define LIST_MAX 6
static unsigned char List[ LIST_MAX ]= { DUP( 123, LIST_MAX ) };

Note: DUPx introduced to enable macro substitution in parameters to DUP

Confident answered 23/7, 2014 at 8:39 Comment(1)
What in the world did I just readPredispose
F
3

The simplest way is to use std::array and write a function template that will return the required std::array with all of its element initialized with the passed argument as shown below.

C++11 Version

template<std::size_t N> std::array<int, N> make_array(int val)
{
    std::array<int, N> tempArray{};
    for(int &elem:tempArray)
    {
        elem = val;
    }
    return tempArray;
}
int main()
{
    //---------------------V-------->number of elements  
    auto arr  = make_array<8>(5);
    //------------------------^---->value of element to be initialized with

    
    //lets confirm if all objects have the expected value 
    for(const auto &elem: arr)
    {
        std::cout << elem << std::endl; //prints all 5 
    }
    
}

Working demo


C++17 Version

With C++17 you can add constexpr to the function template so that it can be used in constexpr context:

//-----------------------------------------vvvvvvvvv--->added constexpr
template<std::size_t N> std::array<int, N> constexpr make_array(int val)
{
    std::array<int, N> tempArray{};
    for(int &elem:tempArray)
    {
        elem = val;
    }
    return tempArray;
}
int main()
{
//--vvvvvvvvv------------------------------>constexpr added
    constexpr auto arr  = make_array<8>(5);

    for(const auto &elem: arr)
    {
        std::cout << elem << std::endl;
    }
    
}

Working demo

Frankly answered 8/11, 2022 at 15:29 Comment(0)
C
1

1) When you use an initializer, for a struct or an array like that, the unspecified values are essentially default constructed. In the case of a primitive type like ints, that means they will be zeroed. Note that this applies recursively: you could have an array of structs containing arrays and if you specify just the first field of the first struct, then all the rest will be initialized with zeros and default constructors.

2) The compiler will probably generate initializer code that is at least as good as you could do by hand. I tend to prefer to let the compiler do the initialization for me, when possible.

Clishmaclaver answered 30/6, 2009 at 20:15 Comment(5)
1) Default initialiszation of POD's is not happening here. Using the list the compiler will generate the values at compile time and place them in a special section of the assembley that is just loaded as part of program initialization (like the code). So the cost is zero at runtime.Moulder
I don't see where he is wrong? int a[100] = { } certainly is initialized to all 0, disregarding to where it appears, and struct { int a; } b[100] = { }; is too. "essentially default constructed" => "value constructed", tho. But this doesn't matter in case of ints, PODS or types with user declared ctors. It only matters for NON-Pods without user declared ctors, to what i know. But i wouldn't cast a down(!) vote because of this. anyway, +1 for you to make it 0 again :)Buckner
@Evan: I qualified my statement with "When you use an initializer..." I was not referring to uninitialized values. @Martin: That might work for constant, static, or global data. But I don't see how that would work with something like: int test(){ int i[10]={0}; int v=i[0]; i[0]=5; return v; } The compiler had better be initializing i[] to zeros each time you call test().Clishmaclaver
it could place data into the static data segment, and make "i" refer to it :)Buckner
True -- technically, in this case it could also elide "i" entirely and just return 0. But using the static data segment for mutable data would be dangerous in multi-threaded environments. The point that I was trying to make in answer to Martin was simply that you can't completely eliminate the cost of initialization. Copy a pre-made chunk from the static data segment, sure, but it's still not free.Clishmaclaver
C
1

In the C++ programming language V4, Stroustrup recommends using vectors or valarrays over builtin arrays. With valarrary's, when you create them, you can init them to a specific value like:

valarray <int>seven7s=(7777777,7);

To initialize an array 7 members long with "7777777".

This is a C++ way of implementing the answer using a C++ data structure instead of a "plain old C" array.

I switched to using the valarray as an attempt in my code to try to use C++'isms v. C'isms....

Covet answered 14/5, 2014 at 1:21 Comment(1)
This is the second worst example of how to use a type I've ever seen...Mele

© 2022 - 2024 — McMap. All rights reserved.