pass different sized 2D arrays to same functions

My experience is as a BASIC programmer and I have been trying to learn some c++.
After spending some time trying to figure out how to pass 2D arrays of different sizes to the same functions I found the c++ way of doing it was something of a pigs breakfast. One way is to treat a 2D array as a 1D array, which is actually how it is stored in memory, and pass the size of the array along with the pointer to the start of that array.
In the example below I have defined three different sized boards. The first two are filled with a character by the fillBoard() function and the third is assigned some char values using the {} brackets.
Now is there a simpler or more readable way of doing it?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>

using namespace std;

void fillBoard(char b[],int ROWS,int COLS,char ch)
{
    for (int row = 0;row < ROWS;row++)
    {
        for (int col =0; col < COLS; col++)
        {
            b[col+COLS*row] = ch;
        }
    }
}

void drawBoard(char b[],int ROWS, int COLS)
{
    for (int row = 0;row < ROWS;row++)
    {
        cout << row+1 << " " ;
        for (int col =0; col < COLS; col++)
        {
            cout << b[col+row*COLS] << " ";
        }
        cout << endl;
    }
    cout << endl;
}

int main()
{

    char board1[6*5];
    char board2[9*4];
    char board3[7*2] = {'1','2','3','4','5','6','7','8','9','A','B','C','D','E'};
    fillBoard(board1,6,5,'.');
    drawBoard(board1,6,5);
    fillBoard(board2,9,4,'*');
    drawBoard(board2,9,4);
    drawBoard(board3,7,2);
    return 0;
}

Last edited on
Nope, that's actually the best way.
fillBoard() can be simplified a bit, though:
1
2
3
4
5
void fillBoard(char b[],int ROWS,int COLS,char ch)
{
    for (int i = 0, n = ROWS*COLS; i < n; i++)
        b[i] = ch;
}
Or even:
1
2
3
4
void fillBoard(char b[],int ROWS,int COLS,char ch)
{
    std::fill(b, b + ROWS * COLS, ch);
}

If the array has been created on the stack and the function will be called from a point where the array declaration is visible, it's possible to do this:
1
2
3
4
template <size_t N>
void fillBoard(char (&b)[N], char ch){
    std::fill(b, b + N, ch);
}
If you change the declarations to actual two-dimensional arrays, it's also possible to do this:
1
2
3
4
5
6
7
8
9
10
11
12
13
template <size_t COLS, size_t ROWS>
void drawBoard(char (&b)[COLS][ROWS]){
    for (int row = 0; row < ROWS; row++)
    {
        cout << row+1 << " " ;
        for (int col =0; col < COLS; col++)
        {
            cout << b[row][col] << " ";
        }
        cout << endl;
    }
    cout << endl;
}
Unfortunately, the final declaration makes for multiple functions in the final object code. You can instead turn that into a little thunk that calls the function correctly. Here's an older posting of mine that builds on the information already given to demonstrate the technique:

Inline thunk to transparently pass any size array to a special array function example
http://www.cplusplus.com/forum/beginner/118058/#msg643956



You can also wrap everything up in a shockingly simple and pretty little class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <ciso646>
#include <stdexcept>

//----------------------------------------------------------------------------
template <typename T>
struct array2d
{
  typedef std::size_t size;
  
  T*         data;
  const size rows;
  const size cols;
  
  template <size ROWS, size COLS>
  array2d( T (&a)[ ROWS ][ COLS ] ): 
    data( &(a[0][0]) ), 
    rows( ROWS ), 
    cols( COLS ) 
    { }

  // unchecked element access
  size index( size row, size col ) const
  {
    return (row * cols) + col;
  }
  
  T* operator [] ( size row ) const
  {
    return &(data[ index( row, 0 ) ]);
  }
  
  // checked element access
  size atindex( size row, size col ) const
  {
    if ((row >= rows) or (col >= cols)) throw std::range_error( "array2d" );
    return index( row, col );
  }
  
  T& operator () ( size row, size col ) const 
  { 
    return data[ atindex( row, col ) ];
  }
};

Which is then very easy to use for any multidimensional array:

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//----------------------------------------------------------------------------
#include <iomanip>
#include <iostream>

//----------------------------------------------------------------------------
// Stupid example 1: print the array
void print( const array2d <const double> & m )
{
  for (unsigned row = 0; row < m.rows; row++)
  {
    for (unsigned col = 0; col < m.cols; col++)
    {
      std::cout << std::setw( 10 ) << m[ row ][ col ];
    }
    std::cout << "\n";
  }
  std::cout << "\n";
}

// Stupid example 2: multiply the array by a scalar value
void mult( const array2d <double> & m, double k )
{
  for (unsigned row = 0; row < m.rows; row++)
  for (unsigned col = 0; col < m.cols; col++)
    m( row, col ) *= k;
}

//----------------------------------------------------------------------------
int main()
{
  double m1[ 3 ][ 4 ] = 
  {
    { 1,  2,  3,  4 },
    { 5,  6,  7,  8 },
    { 9, 10, 11, 12 }
  };
  
  const double m2[ 1 ][ 3 ] =
  {
    { 3.141592, -74, 1.414 }
  };
  
  array2d <const double> m3( m2 );
  
  std::cout << "m1:\n";
  print( m1 );
  
  std::cout << "m1*2:\n";
  mult( m1, 2 );
  print( m1 );
  
  std::cout << "m2:\n";
  print( m2 );
  
  #if 0
  // Won't compile: 'const double' cannot be automatically converted to 'double'
  std::cout << "m2*2:\n";
  mult( m2, 2 ); 
  print( m2 );
  #endif
  
  std::cout << "m3 is a " << m3.rows << " rows by " << m3.cols << " columns copy of m2:\n";
  print( m3 );
}

(This kind of stuff will eventually wind-up in the FAQ.)
Topic archived. No new replies allowed.