Because Foo
has no default constructor, you cannot initialize a Foo
array without C++11's list-initialization.
Your first option is to use a version of C++ that isn't two decades old, but I suppose that's not an option for you, otherwise you would not be asking this.
There are a few other options, but none of them (as far as I know) allow you to keep the array intact.
Use dynamic allocation
struct BarNew {
Foo* foos;
BarNew(int i) : foos(static_cast<Foo*>(::operator new(sizeof(Foo) * 2)))
{
new (foos + 0) Foo(i+1, i+2);
new (foos + 1) Foo(i+2, i+3);
}
};
This can look complicated because you cannot use new
directly without calling the constructor, and that's not possible for an array (again, only prior to C++11). Calling ::operator new
allocates without initializing, just like malloc
does in C. You then need to use placement-new to actually construct each object.
Use std::vector
struct BarVector {
std::vector<Foo> foos;
BarVector(int i)
{
foos.reserve(2);
foos.push_back(Foo(i+1, i+2));
foos.push_back(Foo(i+2, i+3));
}
};
This is the simplest option, with the main drawback being that is also uses dynamic allocation, unlike an array. The call to reserve
is of course optional.
Use an array wrapper
template<class T, std::size_t N>
struct ArrayWrapper {
union {
char buffer[sizeof(T)];
unsigned long long dummy;
} storage[N];
T& operator[](std::size_t i) {
return reinterpret_cast<T&>(storage[i]);
}
};
struct BarStruct {
private:
static ArrayWrapper<Foo, 2> foos_init(int i) {
ArrayWrapper<Foo, 2> foos;
foos[0] = Foo(i+1, i+2);
foos[1] = Foo(i+2, i+3);
return foos;
}
public:
ArrayWrapper<Foo, 2> foos;
BarStruct(int i) : foos(foos_init(i))
{
}
};
This is the most complicated of these three options, but it has the advantage of not using dynamic allocation. It can be made to behave much like a simple array.
The ArrayWrapper
struct here is generic, so you can use it for types other than Foo
. It contains a buffer (array of char
) to contain each Foo
object, and uses a union for alignment purposes, because alignas
is, again, a C++11 addition. I'm not 100% confident it works in every case; I think you have to pray that unsigned long long
has a good enough alignment. If sizeof(T) < sizeof(unsigned long long)
, you will be wasting some space, but you could write a template specialization if that worries you.
foos_init
exists only to provide the initialization values. After the constructor, you can use foos
like an array.
See that all three options compile in C++03.
There is yet another option I can think of, but I'm not confident enough that it would work or in my abilities to write it, so I'm leaving it as an idea. It's a combination of options 2 and 3, where you would provide a storage on the stack just like ArrayWrapper::storage
, but then use it for a std::vector
thanks to the "allocator" feature (the second template argument of std::vector
). That way, you get all the fancy methods of a std::vector
without allocating anything on the heap.