Perhaps something like this?
Yes: This utilized the black magic of undefined behavior.
However: AFAIK this is the only way to do this statically as the question is asked.
Q: Is this a good idea or should it be done differently?
A: It depends on the situation.
struct Buff {
int len;
char const *ptr;
};
struct Collection {
int num;
Buff buffers[1];
};
template <unsigned S>
struct CollectionSize : public Collection {
private:
Buff extraspace[t-1];
};
static const Collection &my_collestions[] = {
CollectionSize<1>(), // add more constructor arguments so you can initialize the base class. the object is const so this is your last chance to do so.
CollectionSize<2>(),
CollectionSize<3>(),
CollectionSize<4>(),
CollectionSize<5>()
};
Explanation:
The trick is you set aside extra space in the derived class via non-type templates but you access it through the base class array. You're over-reading into the next object but the memory WILL be there, and YES it is undefined behavior, but in practice this will work. You also make the array be of const references
not objects themselves or pointers. You need reference or pointers so that the objects can be sliced to their base class because the array needs to be of homogeneous type. It's VERY IMPORTANT this be a reference that is const and nothing else because the C++ standard has special rules around const references to temporary objects (that is, it says they stick around as long as the reference is around), this avoids you having to call new
to dynamically allocate the CollectionSize
objects.
Recap of the key components:
- Unnamed temporary objects referenced by const references const references stick around (remain valid objects, still allocated, with the destructor not-yet-called) as long as the reference is in scope (in this case the static reference is in scope until the program exits) as const objects (you can't write to them).
- Making the objects of a derived templated type means you can allocate more space internal to the object for the base class to use (this is undefined by the standard, but works with all compilers AFAIK in practice).
- Because of somewhat standard in practice compiler defined object layouts of single inheritance classes without virtual pointers, it is known the memory for the derived class lies directly after the last member, the array, of the derived class. Accessing this memory is undefined by the standard but will work in practice.
EDIT:
I've received some criticism on this answer so I'm posting some additional comments to address it
I prefaced this answer with the fact that it may dabble in undefined but is pretty ok-in-practice. This IS how compilers do it! So, realistically the only 2 things we have to worry about are:
- Object layout and making sure there is enough memory for the array to access. While object layout is not defined in C++, compiler vendors do a pretty good job of making this known, and it's consistent, at least for single inheritance of classes without virtual pointers.
- Members WILL BE laid out in order.
- The derived class WILL BE in memory AFTER the base class.
So even if there is padding it's not going to matter because there needs to be at least this much space at the end of the object, and you can manipulate it how you will in a consistent manner though the base class.
- The rules in C++ about what happens when you access memory you don't own. This array trick is a very common thing in C. Usually it takes the form of a simple struct with an array as the last member of size 0 or 1 element and the programmer calls malloc for a larger size then accesses the off the end memory though struct's array.
In actual implementations and my own experience I've never come across a case where either do not behave as expected. Is this the best choice for the programmer to make?.. Well likely it's not, but again it depends. Maybe STL vectors, or even the extra layer of indirection of a pointer are just not an option on some random low-end system. And maybe these objects really do need to be in an array for the sake of elegant code.
In any case, the way the question was asked was how to make such structures statically initialized, and not weather or not it's a good idea. AFAIK this is the best solution to do that. However other solutions that do this better are of course welcome.