What is a good pattern for array template compatibility?

212 Views Asked by At

I would like to create objects having a variable-length array of elements, and have them be compatible in a base/derived-class sense. In C, one could put an indeterminate array at the end of a struct and then just malloc the object to contain the full array:

struct foo {
    int n;
    double x[];
} ;

struct foo *foo1 = (foo *)malloc( sizeof( foo ) + sizeof( double[4] ) );
struct foo *foo2 = (foo *)malloc( sizeof( foo ) + sizeof( double[100] ) );

In c++, it seems like you could do:

template <unsigned I>
class foo {
public:
    int n;
    double x[I];
} ;

but:

auto foo1 = new foo<4>( );
auto foo2 = new foo<100>( );

if (foo1 == foo2) cerr << "incompatible pointers";

You can do it with a common base class, but is that necessary? I just want to use foo1 and foo2, where each object knows the length of its array.


My application is for an ESP32 microcontroller running FreeRTOS. It has limited, non-virtual RAM and a slightly complicated allocation system because of differing capabilities of various chunks of memory (some is slower, some can't contain executable code, some can't be accessed by DMA, etc.) So allocating multiple chunks for pieces of an object (for example, by using std::vector for the array of double at the end) becomes complicated.

I know the length of the double array at object construction time, but I would like the header and the array to be in a single allocated block of memory (so it can have the characteristics I need for it to have later).

The C-style way of doing it would be fine, but it would be nice to have C++ features like iteration over the array (for various objects, which will each have different numbers of doubles). Plus, a native C++ solution would allow me to have objects in the x[] array, instead of fooling around with placement new in raw allocated memory. So, for example:

auto a[] = { new foo<5>( ), new foo<10>( ), new foo<15>( ) };

for (auto i : a)
    for (auto j : i.x)
        cout << log10( j );    // prints 40 logs of doubles

(I expect that's got C++ syntax errors, but hopefully it communicates the idea. I can figure out the syntax for that, if I could get all the foos into a common container.)

1

There are 1 best solutions below

6
On BEST ANSWER

As a low-level C++ developer, I understand exactly what you need, and sadly, there is no replacement for flexible array members in standard C++, with or without templates. You have to keep using flexible array members via compiler extensions.

They are not included in the language since in their current form, they are essentially a hack. They don't do well with inheritance or composition.

The problem with templates is that, the flexible array version has a common type of arrays of all sizes. That means you can place them in arrays, have non-template functions take them as parameters etc:

foo* make_foo(int n);

foo* foos[] = { make_foo(1); make_foo(2); make_foo(3); }; // ok

void take_foo(foo*);

With the template version, types foo<1> and foo<2> are completely unrelated, so you cannot put them in arrays or have non-template functions that take them:

template <int N>
foo<N>* make_foo();

auto foos[] = { make_foo<1>(), make_foo<2>(), make_foo<3>() }; // ill-formed

template <int N>
void take_foo(foo<N>*);

std::array won't help in this discussion, and inheriting from one will still have the problem of having unrelated types.

However, since this is still C++ (albeit non-standard), you can at least have some additional niceties:

template <class T>
struct flex_array {
  int n;
  T data[];

  T* begin() { return &data[0]; }
  T* end() { return begin() + n; }
};


void iterate(flex_array<double>& f) {
    for (double j : f) {
        cout << log10(j); // print however many doubles are in f
    }
}