0

The following works fine in C++11, but doesn't work in C++03.

struct Foo {
    int a, b;
    Foo(int a, int b) : a(a), b(b) {}
};

struct Bar {
    Foo foos[2];
    Bar(int i) :
        foos{ {i + 1, i + 2}, {i + 2, i + 3} },
    {}
};

int main() {}

What could be a C++03 workaround?

This question is quite similar, but it doesn't really address the same usecase. Here I would like to build the subclass from Bar arguments.

nowox
  • 25,978
  • 39
  • 143
  • 293
  • The mentionned question doesn't really answer this one. It doesn't provide any C03++ example. – nowox Apr 15 '23 at 12:19
  • @Jason I'm almost certain the question you linked does not provide a workaround for when `Foo` has no default constructor (it's about an `int` array instead). It just says it's not valid syntax. – Nelfeal Apr 15 '23 at 12:40
  • @Nelfeal That is what I mean, that it is not possible in c++03 to "initialize" as op wants. You can do "assignment" though in c++03. – Jason Apr 15 '23 at 13:20
  • @Jason But OP specifically asks for a workaround... Doing assignments here is not a workaround since you still can't default-initialize `foos`. – Nelfeal Apr 15 '23 at 13:25

1 Answers1

1

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.

Nelfeal
  • 12,593
  • 1
  • 20
  • 39