0

I have a matrix in C++ defined as a std::array of std::array that I would like to set uniformly to a given value. I can't find something as simple as C-style memset for C-Style array (int a[10][10] etc...).

I tried something using std::memset but did not worked (got weird stuff in the array).

#include <stdint.h> // uint16_t
#include <cstring>  // using std::memset ?
#include <iostream> // to print values

// To display the values
template <typename T, std::size_t SIZE>
void Display2D(const std::array< std::array<T, SIZE>, SIZE> &matrix)
{
    for (int l = 0; l < matrix.size(); l++)
    {
        for (int c = 0; c < matrix[l].size(); c++)
        {
            std::cout << (int)matrix[l][c] << " ";
        }
        std::cout << std::endl;
    }
}



// Initialize the matrix with some values
constexpr uint8_t n = 3;
std::array<uint16_t, n> l1{11, 12, 13};
std::array<uint16_t, n> l2{21, 22, 23};
std::array<uint16_t, n> l3{31, 32, 33};
std::array< std::array<uint16_t, n>, n> matrix{l1, l2, l3};

std::cout << "Initial Matrix:" << std::endl;
Display2D(matrix);

// Try to reset it uniformly to a given value
std::memset(&matrix, (uint16_t) 4, n * sizeof(matrix[0]));

std::cout << "Matrix reset:" << std::endl;
Display2D(matrix);

In output I got :

Initial Matrix:
11 12 13
21 22 23
31 32 33
Matrix reset:
1028 1028 1028
1028 1028 1028
1028 1028 1028

I can't figure out what's wrong in my code, and what I should do to reset my matrix.

Additional debug info to help you helping me:

  • if I memset with value = 0, my code prints a matrix full of 0 (cool =) )
  • if I memset with value = 1, my code prints a matrix full of 257
  • if I memset with value = 2, my code prints a matrix full of 514
  • if I memset with value = 3, my code prints a matrix full of 771

I can see the power of 2 in this, probably related to the fact that I uses std ints (uint16_t), by my brain is dead right now, I can't figure it out.

Pierre Baret
  • 1,773
  • 2
  • 17
  • 35
  • Your memset exhibits UB because matrix is of type `std::array` but you are trying to access parts of it as `std::uint16_t`. – bitmask Aug 31 '21 at 12:09
  • @bitmask can you elaborate please ? I'm not sure I understand the point – Pierre Baret Aug 31 '21 at 12:15
  • 2
    Basically `matrix` is not an object of type `std::uint16_t`. Therefore you cannot access it as such (whether to read or to write). How the compiler lays out data in memory is irrelevant from a language point of view. – bitmask Aug 31 '21 at 12:29

3 Answers3

4

Could you not use range based for loops to set each element individually?

for(auto& arr : matrix)
    for(auto& elem : arr)
        elem = 4;

Or, if you want to use standard library algorithms, you could use fill on each of the arrays in the matrix:

for(auto& arr : matrix)
    std::fill(arr.begin(), arr.end(), 4);
fortytoo
  • 452
  • 2
  • 11
  • I could but I was hoping that a more direct solution would exist as a memset is usually more efficient that nested for-loops – Pierre Baret Aug 31 '21 at 12:14
  • 2
    Edited my answer to use std::fill on the individual arrays, should compile to about the same machine code as memset and is modern C++ standard compliant (I hope). – fortytoo Aug 31 '21 at 12:17
  • There is also a fill function inside std::array class that I can use with this approach. Which one would be more efficient ? – Pierre Baret Aug 31 '21 at 12:22
  • 2
    This is not a question of efficiency, all of this is trivial for a modern compiler to optimize. So you will end up with the same machine-code, no matter how you do it. Strictly speaking you are right though, you should prefer the class method if one is provided. – Tiger4Hire Aug 31 '21 at 12:26
  • 1
    @PierreBaret Memset is only "efficient" at the byte-level because it takes alignment of the underlying data into account, which is otherwise lost on char buffers. `std::fill` works at the type-level, which knows the underlying type's exact alignment, and can deduce the underlying contiguity which allows the compiler to perform better optimizations such as assigning `uint64_t` values in-place over top of 4 `uint16_t` values, if not transforming to `memset` ifself if needed. – Human-Compiler Aug 31 '21 at 12:29
  • 1
    A good compiler would compile both approaches down to the same instructions. They are the same operations so I wouldn't imagine there would be any differences in efficiency. See here: https://godbolt.org/z/coY1MEGf6 – fortytoo Aug 31 '21 at 12:30
2

The problem with C is that it's not very type-safe, if you read the description of memset, you will see that it only works at the byte-level.

You want to use std::fill which is type-aware, and will work with any type. Some people are frightened by the apparent overhead, but a decent compiler knows how to remove the overheads, see here

Tiger4Hire
  • 1,065
  • 5
  • 11
  • So finally a oneshot approach would be this line : std::fill(matrix[0].begin(), matrix[2].end(), (uint16_t)4); but isn't it kind of dirty ? (looks like Python... urk) – Pierre Baret Aug 31 '21 at 12:25
0

This line:

std::memset(&matrix, (uint16_t) 4, n * sizeof(matrix[0]));

Doesn't make sense as matrix isn't trivial like an ordinary C-array that you can simply fill in.

This is because you are also treading onto other members that are present in the std::array<T> class, so your code exhibits Undefined Behavior such that the values that the array holds can differ in different implementations.

For a simple one-liner solution, I usually define a handy helper function to fill std::arrays recursively.

#include <type_traits>

template <typename T>
struct is_std_array : std::false_type {};

template <typename T, size_t N>
struct is_std_array<std::array<T, N>> : std::true_type {};

template <typename T, typename X, size_t N>
std::enable_if_t<!is_std_array<T>::value> fill_arr(std::array<T, N>& arr, X const& val) {
    arr.fill(val);
}

template <typename T, typename X, size_t N>
std::enable_if_t<is_std_array<T>::value> fill_arr(std::array<T, N>& arr, X const& val) {
    for (auto& e : arr)
        fill_arr(e, val);
}

Now you can do something like this:

fill_arr(matrix, 4);

As a bonus, it also works for std::arrays of any dimension.

Ruks
  • 3,886
  • 1
  • 10
  • 22