'Pass matrix of unknown dimensions to C++ function

The following code compiles with gcc but not g++. Is it possible to write a function with a matrix argument of arbitrary dimensions in C++?

void print_mat(const int nr, const int nc, const float x[nr][nc]);
#include <stdio.h>

void print_mat(const int nr, const int nc, const float x[nr][nc])
{
    for (int ir=0; ir<nr; ir++) {
        for (int ic=0; ic<nc; ic++) {
            printf(" %f",x[ir][ic]);
        }
        printf("\n");
    }
}


Solution 1:[1]

To build on Peter’s answer, you can use the single-dimension variant with proper indexing to do the work. But you can make invoking the function much nicer in C++:

void print_mat(const int nr, const int nc, const float *x)
{
  ...
}

template <std::size_t NumRows, std::size_t NumColumns>
void print_mat(const float (*x)[NumRows][NumColumns])
{
  print_mat((int)NumRows, (int)NumColumns, (const float *)x);
}

Now you can use the function naturally:

float matrix[4][3] = { ... };

print_mat( matrix );

This only works, however, as long as you do not let the array downgrade to a pointer.

Also, there are limit issues with the cast from size_t to int, but it really shouldn’t be possible to make one big enough that it would matter.

EDIT: There are also potential buffering/alignment issues when casting a multidimensional array to a one-dimensional, flat array. But no common, modern compiler + hardware that I am aware of where this is an issue. Just be sure to know your target platform.

Solution 2:[2]

As noted in comments, C++ does not support variable-length arrays (VLAs). C did from the 1999 standard, but that became optional in C11. In combination, those factors are relevant to why gcc (depending on version) accepts your code, but g++ does not.

In C (and C++ if writing in a C style <blech!>), an alternative is to pass a single-dimensional array (with contiguous elements) to a function that accepts a pointer and use an indexing scheme to access elements. For example, assuming row-major ordering;

void print_mat(const int nr, const int nc, const float *x)
{
    for (int ir=0; ir<nr; ir++)
    {
        int row_start = ir * nc;
        for (int ic=0; ic<nc; ic++)
        {
            printf(" %f",x[row_start + ic]);
        }
        printf("\n");
    }
}

In C++, one can use - depending on which (if any) dimensions are known at compile time;

  • std::array<std::array<float, nc>, nr> (if array dimensions nc and nr are both fixed at compile time);
  • std::vector<std::vector<float> > (if neither dimension is known until run time). Bear in mind that individual std::vector<float>s in a std::vector<std::vector<float> > CAN have different dimensions. Your caller will need to ensure dimensions are the same for all contained std::vector<float>s and/or your function will need to check sizes.

If nc is fixed at compile time but nr is not, you can use std::vector<std::array<float, nc> >. If nr is fixed at compile time, but nc is not, you can use std::array<std::vector<float>, nr>.

If you must pass the entire vector/array, usually better to pass it by reference than by value. For example;

void print_mat(const std::array<std::array<float, nc>, nr> &)
{
     // definition
}

or (if you need to pass around some arrays of different dimensions) create a family of such functions

template<int nc, int nr>
void print_mat(const std::array<std::array<float, nc>, nr> &)
{
     // definition
}

Personally, I would not actually pass arrays or vectors around. I'd use iterators, such as;

template<class NestedIterator>
void print_mat(NestedIterator row, NestedIterator end_row)
{
     while (row != end_row)
     {
           auto col = std::begin(*row);     // Assuming C++11 and later
           auto col_end = std::end(*row);
           while (col != col_end)
           {
               std::cout << ' ' << *col;
               ++col;
           }
           std::cout << '\n';   // or std::endl
           ++row;
     }
}

This function assumes begin and end iterators from a container that contains (nested) containers (so passing iterators from a std::vector<float> will be a diagnosable error). It works for any type of element (e.g. is not limited to float in your case), that can be streamed to a std::ostream.

I've assumed row-major ordering in the above. The adjustments for column-major ordering are trivial.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Dúthomhas
Solution 2