21

My embedded system got a C++11-capable version of g++, so I've been cleaning up code from

for( uint16_t* p = array; p < (&array)[1]; ++p ) {
    *p = fill_value;
}

to

for( uint16_t& r : array ) {
    r = fill_value;
}

which is much more readable.

Is there a range-based for loop which operates over all elements of array2[m][n]?

The old version is

for( int16_t* p = array2[0]; p < (&array2)[1][0]; ++p ) {
    *p = fill_value;
}

and I don't want nested loops, unless it's guaranteed the compiler will flatten them.

(FWIW, the compiler is the GNU 4.7.4 Linaro g++ ARM cross-compiler that ships with TI Code Composer Studio 6.0.0)

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 1
    @πάνταῥεῖ: Using `std::array`, I could write `for( p = array2.front().begin(); p < array2.back().end(); ++p )` which is a big improvement, but still not as nice as a range-based for – Ben Voigt Jun 19 '14 at 22:30
  • "*and I don't want nested loops, unless it's guaranteed the compiler will flatten them.*" Mind if I ask why? Isn't is sufficient that the compiler will flatten them if that's beneficial? – David Schwartz Jun 19 '14 at 22:34
  • If you can use Boost, and can replace those arrays with Boost.MultiArray, I'm pretty sure there's a way to get a one-dimensional *view* of a multi-dimensional array. You can then iterator over the view using a range-based `for`. – Praetorian Jun 19 '14 at 22:35
  • @David: It's an embedded system. I try not to do things that gratuitously increase instruction count. – Ben Voigt Jun 19 '14 at 22:36
  • @BenVoigt Then, presumably, so does your compiler. Otherwise, you've chosen a poor compiler. You should code what you mean first, and worry about these details only if you have evidence the compiler got something wrong. – David Schwartz Jun 19 '14 at 22:42
  • Why not `std::fill(std::begin(array), std::end(array), fill_value);`? – Kerrek SB Jun 19 '14 at 22:43
  • @KerrekSB: I fail to see how that helps with the hard case (2-D array). – Ben Voigt Jun 19 '14 at 22:44
  • @DavidSchwartz: I have expert knowledge that multi-dimension arrays are stored contiguously and can be iterated that way. Doesn't mean that compiler vendors have gone to the trouble of making a peephole optimization for this case. (Although I'd be interested to see evidence that any have) – Ben Voigt Jun 19 '14 at 22:46
  • @BenVoigt i'm not sure that that is well-defined? maybe a language-lawyer question is in order – M.M Jun 19 '14 at 22:49
  • @MattMcNabb: Why wouldn't it be? The conversions here on one-beyond-the-end objects are array-to-pointer conversion, not lvalue-to-rvalue conversions. – Ben Voigt Jun 19 '14 at 22:51
  • well, you iterate off the end of one `std::array`, I'm not sure it is guaranteed that the iterator remains valid once it passes the end – M.M Jun 19 '14 at 22:52
  • @BenVoigt: It doesn't, it's just a comment on general code cleanup... – Kerrek SB Jun 19 '14 at 22:52
  • @MattMcNabb: The arrays in the question aren't `std::array`, only one comment in response to a now-deleted comment. – Ben Voigt Jun 19 '14 at 22:56
  • I was responding to "Using std::array I could write....front().begin()....back.end()", I'm assuming you meant `std::array< std::array, M >` – M.M Jun 19 '14 at 23:01
  • @Matt: Yes, πάνταῥεῖ had suggested using `std::array`. – Ben Voigt Jun 19 '14 at 23:47
  • yes, my comment is saying that your suggestion, in that comment, of using front.begin()...back().end() might not be well-defined – M.M Jun 19 '14 at 23:48
  • @MattMcNabb: Ahh, ok. The pointer iteration is well-defined, all the elements are within the same aggregate object. Iterators on individual `std::array` objects might not be so forgiving. So I guess it would be `for( p = &array2.front().front(); p <= &array2.back().back(); ++p )` – Ben Voigt Jun 19 '14 at 23:51
  • @David: So I checked the assembly+source listing. The optimizer doesn't flatten the nested loops (when run with `-O2 -Os`) – Ben Voigt Jun 20 '14 at 21:19
  • why not just memset if always the same value? – paulm Jun 22 '14 at 09:14
  • @paulm: `memset` requires all bytes, not just all elements, to be the same. – Ben Voigt Jun 22 '14 at 16:37
  • which seems to be the requirement here, an array of basic POD types (uint16_t) – paulm Jun 22 '14 at 20:26
  • @Paulm: What I'm saying is that `memset` can fill `0xABAB` but not `0xABCD`. – Ben Voigt Jun 22 '14 at 20:43

7 Answers7

16

As an example, there are various ways to print and manipulate value of a multidimensional array.

int arr[2][3] = { { 2, 3, 4 }, { 5, 6, 7} };  

First Method,

size_t count = 0 ; 

for( auto &row : arr)
    for(auto &col : row)
         col = count ++; 

Here in the first for loop we are referring to the two array. Then in the second array we have reference to the 3 elements of those subarrays separately. And we are also assigning count to col. So, it iterates over to the next element of the subarray.

Second Method,

for( auto &row : arr)
     for( auto col : row)
          cout << col << endl; 

We take reference here in the first loop because we want to avoid array to pointer conversion.

If this is done( error case: first for loop is not a reference ),

for( auto row : arr)          // program won't compile
     for( auto col : row)

Here, we have int * in row. By the time we reach the second for loop. Because row is now int * and not a list the program will not compile. You have to create a list then only we can pass that it to ranged for loop and use it for iterating over that list.

vector<int> list = { *(row+0) , *(row+1) , *(row+ 2) } ;

Now we can use the list for iteration

for ( ----- : list)
abhimanyuaryan
  • 3,882
  • 5
  • 41
  • 83
  • what will be the actual datatype of row and col? ( declared as auto ) – Mah35h Aug 15 '21 at 15:21
  • @Mah35h The outer loop iterates `arr`(`int[2][3]`). Each inner array(`int[3]`) will be assigned to `row` by copy. By copy means that `int[3]` is converted to a pointer to its first element. So `row` is `int*`. With just `auto`, we can't determine the `col`'s type because it doesn't compile. – rosshjb Nov 07 '21 at 07:48
8
for ( auto &a : array )
{
   for ( int &x : a ) x = fill_value;
}

EDIT: You can try the following

const size_t n = 2;
const size_t m = 3;

int a[n][m] = { { 1, 2, 3 }, { 4, 5, 6 } };

for ( auto &x : reinterpret_cast<int ( & )[n * m]>( a ) )  x = 10;
for ( auto x : reinterpret_cast<int ( & )[n * m]>( a ) )  std::cout << x << ' ';
std::cout << std::endl;;

The output is

10 10 10 10 10 10 

The advantage of this approach is that you can reinterpret any multidimensional array not only a two-dimensional array. For example

int a[n][m][k] = { /* some initializers */ };

for ( auto x : reinterpret_cast<int ( & )[sizeof( a ) / sizeof( ***a )]>( a ) )
{
    std::cout << x << ' ';
}
std::cout << std::endl;;
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 3
    *I don't want nested loops, unless it's guaranteed the compiler will flatten them.* - got any guarantees for the compiler he's using? – Praetorian Jun 19 '14 at 22:32
  • 1
    I don't like repeating the size, but it looks like this method is at least safe, see http://stackoverflow.com/a/15284276/103167 – Ben Voigt Jun 19 '14 at 22:50
  • @Ben Voigt There is an advantage of this method compared with a template class that you can reinterpret an array of ant dimensions.:) – Vlad from Moscow Jun 19 '14 at 22:53
  • @Vlad: I agree that's a nice feature. Oh, `n * m` can be replaced by `sizeof a / sizeof **a` – Ben Voigt Jun 19 '14 at 22:54
  • @Ben Voigt Or for example sizeof a / sizeof ***a and so on. – Vlad from Moscow Jun 19 '14 at 22:56
  • @BenVoigt that linked question is about aliasing a 1-D array as a multi-D array, not the other way around. `[expr.add]#5` says fairly clearly that the reverse case is UB (your "old version" of 2-D array iteration from the original post is UB for the same reason) – M.M Jun 19 '14 at 23:09
  • @Matt: I'm pretty sure you're incorrect. Your interpretation would be correct if the rule said "array subobject", but it does not. – Ben Voigt Jun 19 '14 at 23:49
  • It says "points to an element of an array object". Subobjects are objects. Further, the elements of `T[m][n]` have type `T[n]` , so a `T *` *cannot* be pointing to those elements. The `T *` can only be pointing to elements of the array object `T[n]`. So "the array object" must refer to the array of type `T[n]`, not the larger aggregate containing that object. [Here is an existing question on the topic](http://stackoverflow.com/questions/8428605/can-i-access-multidimensional-array-using-a-pointer) , we should post there instead of discussing this in comments – M.M Jun 19 '14 at 23:52
  • @MattMcNabb: Even under the most strict interpretation, because I have a stride of 1, it is allowed. When I reach the end of one subarray, going off the end by one is permitted as a special case. Now I can add one again, because pointers before and after are both within the bounds of the same object (the next subarray). – Ben Voigt Jun 19 '14 at 23:56
  • You're assuming that a past-the-end pointer for one array subobject also constitutes a pointer to the first element of the next array subobject. I don't think the latter is guaranteed ; it's certainly not explicitly stated (nor is it explicitly denied AFAICS). It *is* explicitly denied by C99 (6.5.6#8) but that has slightly different wording than C++ does. AFAICS again, C++ never says that past-the-end iterators are never dereferencable; it only says that they might not be. – M.M Jun 20 '14 at 00:10
  • @Matt: C++ explicitly acknowledges that the one-past-the-end might be a another object. In a footnote: "When viewed in this way, an implementation need only provide one extra byte (which might overlap another object in the program) just after the end of the object in order to satisfy the “one past the last element” requirements." In a multi-dimensional array, this "might" becomes a certainty. – Ben Voigt Jun 20 '14 at 00:47
3

Here's some code that will fill an arbitrary array (of statically known size):

#include <algorithm>
#include <iterator>
#include <type_traits>

template <typename T>
void fill_all(T & a, typename std::remove_all_extents<T>::type v);

template <typename T>
void fill_all_impl(T & a, typename std::remove_all_extents<T>::type v, std::false_type);

template <typename T>
void fill_all_impl(T & a, typename std::remove_all_extents<T>::type v, std::true_type)
{
  for (auto & x : a)
    fill_all(x, v);
}

template <typename T>
void fill_all_impl(T & a, typename std::remove_all_extents<T>::type v, std::false_type)
{
  std::fill(std::begin(a), std::end(a), v);
}

template <typename T>
void fill_all(T & a, typename std::remove_all_extents<T>::type v)
{
  fill_all_impl(a, v, std::is_array<typename std::remove_extent<T>::type>());
}

Example usage:

int a[3][4][2];
fill_all(a, 10);
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
1

Combining parts of Vlad's and Praetorian's answers, I decided to use:

template<typename T, size_t N, size_t M>
auto flatten(T (&a)[M][N]) -> T (&)[M*N] { return reinterpret_cast<T (&)[M*N]>(a); }

for( int16_t& r : flatten(array2) ) {
    r = fill_value;
}
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
1

A bit more general variant of @Ben Voigt's answer (which can be applied to n-dimensional arrays):

template
    <
        typename Array,
        typename Element = typename std::remove_all_extents<Array>::type,
        std::size_t Size = sizeof(Array) / sizeof(Element),
        typename FlattenedArray = Element (&)[Size]
    >
constexpr FlattenedArray Flatten(Array &a)
{
    return reinterpret_cast<FlattenedArray>(a);
}

template
    <
        typename Array,
        typename Element = typename std::remove_all_extents<Array>::type
    >
void FillArray(Array& a, Element v)
{
    for (Element& e : Flatten(a))
    {
        e = v;
    }
}

// ...

int a[2][3][5];
int d = 42;

FillArray(a, d);

Live example.

Community
  • 1
  • 1
Constructor
  • 7,273
  • 2
  • 24
  • 66
0

A bit simpler (and possibly less effective) solution than @Kerrek SB's one:

#include <type_traits>

template <typename Type>
void FillArray(Type& e, Type v)
{
    e = v;
}

template <typename Type, std::size_t N>
void FillArray(Type (&a)[N], typename std::remove_all_extents<Type>::type v)
{
    for (Type& e : a)
    {
        FillArray(e, v);
    }
}

Example of use:

int a[2][3][5];

FillArray(a, 42);

A little more general solution which allows to apply a functor to all elements of a multidimensional array:

template <typename Type, typename Functor>
void ForEachElement(Type& e, Functor f)
{
    f(e);
}

template <typename Type, std::size_t N, typename Functor>
void ForEachElement(Type (&a)[N], Functor f)
{
    for (Type& e : a)
    {
        ForEachElement(e, f);
    }
}

Example of use:

int a[2][3][5];

ForEachElement(a, [](int& e){e = 42;});
Community
  • 1
  • 1
Constructor
  • 7,273
  • 2
  • 24
  • 66
-1
#include <iostream>
using namespace std;

int main() {
    int arr[3][4];
    
    // Initialising the 2D array
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 4; j++){
            arr[i][j] = i * j + 10;
        }
    }
    
    // Accessing elements of 2D array using range based for loop
    for(auto &row: arr){
        for(auto &col: row){
            cout << col << " ";
        }
        cout << endl;
    }
    
    return 0;
}
Aaqib Javaid
  • 303
  • 2
  • 5