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");
}
}
CodePudding user response:
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 dimensionsnc
andnr
are both fixed at compile time);std::vector<std::vector<float> >
(if neither dimension is known until run time). Bear in mind that individualstd::vector<float>
s in astd::vector<std::vector<float> >
CAN have different dimensions. Your caller will need to ensure dimensions are the same for all containedstd::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.
CodePudding user response:
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.