21

Here is what I am using:

class something { char flags[26][80]; } a; std::fill(&a.flags[0][0], &a.flags[0][0] + 26 * 80, 0); 

(Update: I should have made it clear earlier that I am using this inside a class.)

6 Answers 6

38

The simple way to initialize to 0 the array is in the definition:

char flags[26][80] = {}; 

If you want to use std::fill, or you want to reset the array, I find this a little better:

char flags[26][80]; std::fill( &flags[0][0], &flags[0][0] + sizeof(flags) /* / sizeof(flags[0][0]) */, 0 ); 

The fill expressed in terms of the array size will allow you to change the dimensions and keep the fill untouched. The sizeof(flags[0][0]) is 1 in your case (sizeof(char)==1), but you might want to leave it there in case you want to change the type at any point.

In this particular case (array of flags --integral type) I could even consider using memset even if it is the least safe alternative (this will break if the array type is changed to a non-pod type):

memset( &flags[0][0], 0, sizeof(flags) ); 

Note that in all three cases, the array sizes are typed only once, and the compiler deduces the rest. That is a little safer as it leaves less room for programmer errors (change the size in one place, forget it in the others).

EDIT: You have updated the code, and as it is it won't compile as the array is private and you are trying to initialize it externally. Depending on whether your class is actually an aggregate (and want to keep it as such) or whether you want to add a constructor to the class you can use different approaches.

const std::size_t rows = 26; const std::size_t cols = 80; struct Aggregate { char array[rows][cols]; }; class Constructor { public: Constructor() { std::fill( &array[0][0], &array[rows][0], 0 ); // [1] // memset( array, 0, sizeof(array) ); } private: char array[rows][cols]; }; int main() { Aggregate a = {}; Constructor b; } 

Even if the array is meant to be public, using a constructor might be a better approach as it will guarantee that the array is properly initialized in all instances of the class, while the external initialization depends on user code not forgetting to set the initial values.

[1] As @Oli Charlesworth mentioned in a comment, using constants is a different solution to the problem of having to state (and keep in synch) the sizes in more than one place. I have used that approach here with a yet different combination: a pointer to the first byte outside of the bidimensional array can be obtained by requesting the address of the first column one row beyond the bidimensional array. I have used this approach just to show that it can be done, but it is not any better than others like &array[0][0]+(rows*cols)

Sign up to request clarification or add additional context in comments.

32 Comments

@DavidRodriguez: Can you explain why a similar syntax i.e. fill(&arr[0], &arr[0] + sizeof(arr), 0) doesn't work for a 1D array?
@DhruvMullick: The sizeof operator gives you the size in bytes, not the number of elements, if arr holds anything such that sizeof(*arr) != 1 the above has undefined behavior and will probably crash (try to access well beyond the end of the array). Otherwise (i.e. if the array holds [unsigned|signed] char, it should be working
The above arithmetic isn't staying inside one array. flags[0][0] is an element of the 80-element array flags[0] but &flags[0][0] + sizeof(flags) takes us well outside of flags[0]. Yes, it would be natural for this to take us into/through the other arrays in flags (flags[1], flags[2], flags[3], etc.) and yes, the standard guarantees the memory layout makes this sensible, but I still don't see what guarantees that this arithmetic is valid when arithmetic taking you outside your starting array normally yields undefined behavior.
FWIW, the discussion here seems to suggest that the arithmetic might be well-defined but only because the data type is char.
@JeJo This can't be done with multidimensional arrays and this can now be shown using core constant evaluation which is very picky about detecting undefined behavior Here's the proof. You can only call std::fill with pointers over the innermost array range.
|
5

What is the safe way to fill multidimensional array using std::fill?

The easy default initialization would be using braced inilization.

char flags[26][80]{}; 

The above will initialize all the elements in the flags to default char.


2-D Array filling using std::fill or std::fill_n

However, in order to provide different value to initialize the above is not enough. The options are std::fill and std::fill_n. (Assuming that the array flags is public in your class)

std::fill( &a.flags[0][0], &a.flags[0][0] + sizeof(a.flags) / sizeof(a.flags[0][0]), '0'); // or using `std::fill_n` // std::fill_n(&a.flags[0][0], sizeof(a.flags) / sizeof(a.flags[0][0]), '1'); 

To generalize this for any 2d-array of any type with any initializing value, I would suggest a templated function as follows. This will also avoid the sizeof calculation of the total elements in the array.

#include <algorithm> // std::fill_n, std::fill #include <cstddef> // std::size_t template<typename Type, std::size_t M, std::size_t N> constexpr void fill_2D_array(Type(&arr2D)[M][N], const Type val = Type{}) noexcept { std::fill_n(&arr2D[0][0], M * N, val); // or using std::fill // std::fill(&arr2D[0][0], &arr2D[0][0] + (M * N ), val); } 

Now you can initialize your flags like

fill_2D_array(a.flags, '0'); // flags should be `public` in your class! 

(See Live Online)


3-D Array filling using std::fill or std::fill_n

Adding one more non-template size parameter to the above template function, this can be brought to 3d-arrays as well

#include <algorithm> // std::fill_n #include <cstddef> // std::size_t template<typename Type, std::size_t M, std::size_t N, std::size_t O> constexpr void fill_3D_array(Type(&arr3D)[M][N][O], const Type val = Type{}) noexcept { std::fill_n(&arr3D[0][0][0], M * N * O, val); } 

(See Live Online)

Comments

2

it is safe, a two-dimensional array is an array of arrays. Since an array occupied contiguous storage, so the whole multidimensional thing will too. So yeah, it's OK, safe and portable. Assuming you are NOT asking about style, which is covered by other answers (since you're using flags, I strongly recommend std::vector<std::bitset<80> > myFlags(26))

4 Comments

Are you certain the bitset would be suitable? I'm just storing either zero or one inside each space in he 2d array. These flags are used to track which positions on the console have been updated with my floodfill routine.
@Truncheon: you store 8 values in a char, right? And in order to get, say, 7th value in the 6th char you must do some bit shifting/anding etc. The bitset will do it for you. That is, it will store each flag in a bit, and you can set/unset each flag by its index without worrying about bit patterns. The only downside is that the bitset's size must be a compile-time constant. If you are familiar with boost, they have dynamic_bitset which is pretty much what its name is.
I believe that he is storing a single bit in each byte. That means that using a bitset of 26*80 positions and the appropriate (row,col)->index algebra the use of a single bitset will be more efficient in memory consumption.
@David: It will definitely be more efficient in terms of memory, but readability will suffer, that is he will have to write btst[numCols*row+col] which is less readable compared to v[row][col]. The decision is up to the OP and is dependent on his priorities.
1
char flags[26][80]; std::fill((char*)flags, (char*)flags + sizeof(flags)/sizeof(char), 0); 

Comments

0

Is char[80] supposed to be a substitute for a real string type? In that case, I recommend the following:

std::vector<std::string> flags(26); flags[0] = "hello"; flags[1] = "beautiful"; flags[2] = "world"; // ... 

Or, if you have a C++ compiler that supports initialization lists, for example a recent g++ compiler:

std::vector<std::string> flags { "hello", "beautiful", "world" /* ... */ }; 

1 Comment

This doesn't try to answer the question, just gives advice that nobody asked for.
0

The problem isn't that you can't fill a 2D array by passing appropriate pointer(s) to a function. It's pretty clear that if you write a function that, for instance, takes a T* and a count of rows*cols and assumes a contiguous set of T's, it will compile and happily fill the 2D array. The function doesn't know anything about the structure of the 2D array. It just gets a pointer and length.

The problem is the compiler knows the structure. And it knows that if you pass a pointer to an element in the 2D array, the only values that can be modified according to the language, are the ones in the sub-array that the pointer addressed. So the compiler can assume all other values outside of that subarray will not be altered. The language guarantees that. And compiler optimizers can take advantage to speed up code.

This is similar to the problem of signed int overflow. While signed overflow is well defined in two's complement arithmetic, compilers are free to assume it never happens because it is prohibited by the language. This opens up optimization opportunities and compilers take advantage to speed up code.

So no, You can't use std::fill to initialize the contents of a multi-dimensional array. The C++ language is rather strict on what pointers are allowed to point to. Even without de-referencing them! The following code shows std::fill failing when trying to initialize the entire array. It also shows that simply incrementing the pointer to one past the last element is UB.

#include <algorithm> constexpr bool foo() { char matrix[2][10]{}; std::fill(&matrix[0][0], &matrix[0][10], 0); // works std::fill(&matrix[1][0], &matrix[1][10], 0); // works // std::fill(&matrix[0][0], &matrix[1][10], 0); // fails, UB detected char* pc = &matrix[0][10]; // Legal. points to one past the last element of the lower, 10 char array. // pc = &matrix[0][10]+1; // UB. points to two past the last element of the lower, 10 char array. return true; } constexpr bool x = foo(); int main() { return x; } 

Code in Compiler Explorer

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.