What you want to do is possible, not not easy, in C++, and the interface to your struct
is not a struct
style interface.
Just like how a std::vector
takes a block of memory and reformats it into something very much like an array, then overloads operators to make itself look array-like, you can do the same.
Access to your data will be via accessors. You'll manually construct your members in the buffer.
You might start with a list of pairs of "tags" and data types.
struct tag1_t {} tag1;
struct tag2_t {} tag2;
typedef std::tuple< std::pair< tag1_t, int >, std::pair<tag2_t, double> > header_t;
then, some more types that we'll interpret as saying "after the header part, we have an array". I'd want to massively improve this syntax, but the important part for now is to build up compile time lists:
struct arr_t {} arr;
std::tuple< header_t, std::pair< arr_t, std::string > > full_t;
You'd then have to write up some template mojo that figures out, given N
at run time, how big a buffer you'd need to store the int
and double
followed by N
copies of the std::string
, everything properly aligned. This isn't easy.
Once you've done that, you'd also need to write code that constructs everything described above. If you wanted to get fancy, you'd even expose a perfect forwarding constructor and constructor wrappers allowing the objects to be constructed in a non-default state.
Finally, write up an interface that finds the memory offset of the constructed objects based on the tags I injected into the above tuple
s, reinterpret_cast
s the raw memory into a reference to the data type, and returns that reference (in both const and non-const versions).
For the array at the end, you'd return some temporary data structure that has overloaded operator[]
which produces the references.
If you take a look at how std::vector
turns blocks of memory into arrays, and mix that with how boost::mpl
arranges tag-to-data maps, and then also mess manually arround with keeping things properly aligned, every step is challenging but not impossible. The messy syntax I've used here can also be improved (to some extent).
The end interface might be
Foo* my_data = Foo::Create(7);
my_data->get<tag1_t>(); // returns an int
my_data->get<tag2_t>(); // returns a double
my_data->get<arr_t>()[3]; // access to 3rd one
which could be improved with some overloading to:
Foo* my_data = Foo::Create(7);
int x = my_data^tag1; // returns an int
double y = my_data^tag2; // returns a double
std::string z = my_data^arr[3]; // access to 3rd std::string
but the effort involved would be reasonably large to get this far, and many of the things required would be pretty horrible.
Basically, in order to solve your problem as described, I would have to rebuild the entire C++/C structure-layout system manually within C++, and once you have done that it isn't hard to inject "arbitrary length array at the end". It would even be possible to inject arbitrary length arrays in the middle (but that would mean that finding the address of structure members past that array is a runtime problem: however, as our operator^
is allowed to run arbitrary code, and your structure can store the length of arrays, we are able to do this).
I cannot, however, think of a simpler, portable way to do what you ask within C++, where the data types stored do not have to be standard-layout.