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

C++ArraysInitializationVariable AssignmentDefault Value

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

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.

Solution 2 - C++

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.

Solution 3 - C++

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.

Solution 4 - C++

C++11 has another (imperfect) option:

std::array<int, 100> a;
a.fill(-1);

Solution 5 - C++

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.

Solution 6 - C++

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.");
}

Solution 7 - C++

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.

Solution 8 - C++

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

Solution 9 - C++

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.

Solution 10 - C++

  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.

Solution 11 - C++

In C++, it is also possible to use meta programming and variadic templates. The following post shows how to do it: https://stackoverflow.com/questions/2978259/programmatically-create-static-arrays-at-compile-time-in-c.

Solution 12 - C++

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

Solution 13 - C++

I wish it was a standard feature:

#include <stdio.h>

 __asm__
 (
"    .global _arr;      "
"    .section .data;    "
"_arr: .fill 100, 1, 2; "
 );

extern char arr[];

int main() 
{
    int i;
  
    for(i = 0; i < 100; ++i) {
        printf("arr[%u] = %u.\n", i, arr[i]);
    }
}

In Fortran you could do:

program main
    implicit none

    byte a(100)
    data a /100*2/
    integer i

    do i = 0, 100
        print *, a(i)
    end do
end

Though, as far as I know, it does not have unsigned numbers.

I wish modern C/C++ had it as a shorthand to doing things like in the code below:

#include <stdio.h>
#include <stdint.h>

/* did I count it correctly? I'm not quite sure. */
uint8_t arr[] = {
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
};    

int main(int argc, char **argv) 
{
    int i;

    for(i = 0; i < 100; ++i) {
        printf("arr[%u] = %u.\n", i, arr[i]);
    }
    
    return 0;
}

If it was an array of 1,000,000 bytes it would be a lot more frustrating, probably making people to opt for doing the filling at runtime using a function.

It's perfectly portable, there's no reason for it not to be in the language.

Just hack it in like:

#include <stdio.h>
#include <stdint.h>

/* a byte array of 100 twos declared at compile time. */
uint8_t twos[] = {100:2};

int main()
{
    uint_fast32_t i;
    for (i = 0; i < 100; ++i) {
        printf("twos[%u] = %u.\n", i, twos[i]);
    }

    return 0;
}

One way to hack it in is via preprocessing... (Code below does not cover edge cases, but is written to quickly demonstrate what could be done.)

#!/usr/bin/perl
use warnings;
use strict;

open my $inf, "<main.c";
open my $ouf, ">out.c";

my @lines = <$inf>;

foreach my $line (@lines) {
    if ($line =~ m/({(\d+):(\d+)})/) {
        printf ("$1, $2, $3");        
        my $lnew = "{" . "$3, "x($2 - 1) . $3 . "}";
        $line =~ s/{(\d+:\d+)}/$lnew/;
        printf $ouf $line;
    } else {
        printf $ouf $line;
    }
}

close($ouf);
close($inf);

note: this is a naive(clumsy knee jerk) answer from 2016, at the time the issues of compatibility didn't hit me, and I really wanted to have this feature and tried to naively "automate" it myself. I no longer want languages to get "cool new features just because I want them" because dealing with backward compatibility from that is a nightmare, and maintaining custom tools to automate conveniences is also not particularly fun.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMilanView Question on Stackoverflow
Solution 1 - C++Evan TeranView Answer on Stackoverflow
Solution 2 - C++CallumView Answer on Stackoverflow
Solution 3 - C++jalfView Answer on Stackoverflow
Solution 4 - C++TimmmmView Answer on Stackoverflow
Solution 5 - C++0x6adb015View Answer on Stackoverflow
Solution 6 - C++David StoneView Answer on Stackoverflow
Solution 7 - C++laaltoView Answer on Stackoverflow
Solution 8 - C++SteenView Answer on Stackoverflow
Solution 9 - C++Steve MelnikoffView Answer on Stackoverflow
Solution 10 - C++BoojumView Answer on Stackoverflow
Solution 11 - C++ingomueller.netView Answer on Stackoverflow
Solution 12 - C++AstaraView Answer on Stackoverflow
Solution 13 - C++DmitryView Answer on Stackoverflow