2

I have a class with a member array. The length is a constant, but this constant is not known until compile time (In my actual code, this constant is defined differently for different compilation targets). The type of the array is a class with no default constructor.

#define CONSTANT 2

class Data {
public:
    Data(int number){}
};

class DemoClass {
private:
    Data _member[CONSTANT];
public:
    DemoClass():
        _member{
            Data(0),
            Data(0)
        }
    {
        // stuff
    }
};

In this example, I can set _member using the initializer list. However, if the value of COSNTANT changes, I have to change that initializer list.

In theory, changing DemoClass to have a default constructor that calls the other constructor with an argument of 0 would work for my case, because I will always call the Data constructor with 0. However, I cannot change DemoClass because it is in an external library.

One solution I've considered is creating the following class:

class CustomData : public Data {
public:
    CustomData() : Data(0){}
};

This works, but it seems a bit complicated. Is there a simpler way to initialize this array?

ItsAmy
  • 844
  • 1
  • 6
  • 20
  • 1
    Use a `std::vector`. It has a constructor that you can provide a value for the number of objects you want to create and an object to copy into all of those objects. – NathanOliver Jul 03 '19 at 12:54
  • I am working in an environment where I want to minimize usage of dynamic memory allocation. Is there a way to get this functionality of `std::vector` without dynamic allocation? – ItsAmy Jul 03 '19 at 12:59
  • You could write/get a stack allocator and use that as the allocator for the vector so it doesn't use the heap. – NathanOliver Jul 03 '19 at 13:00
  • Using initializer list this way implies that you want to initialize every member manually. If you want to initialize all elements to one value, loop over the elements and assign that value to them. – Rinat Veliakhmedov Jul 03 '19 at 13:03
  • @RinatVeliakhmedov Is this possible? I was under the impression that, because `Data` has no default constructor, I _must_ initialize it in the initializer list. – ItsAmy Jul 03 '19 at 13:05
  • 1
    You literally have `Data()` there, that's what default constructor is. EDIT: you had it there before you edited that away, anyway. – Rinat Veliakhmedov Jul 03 '19 at 13:06
  • Yes, I just realized that I accidentally left the default constructor in. I have edited my question. Sorry for the confusion! – ItsAmy Jul 03 '19 at 13:07

3 Answers3

3

I found the answer to your problem here. So, in your case this solution should be applied like that:

#include <utility>
#include <array>

#define CONSTANT 2

class Data {
public:
    Data(int number){}
};

template<typename T, size_t...Ix, typename... Args>
std::array<T, sizeof...(Ix)> repeat(std::index_sequence<Ix...>, Args &&... args) {
   return {{((void)Ix, T(args...))...}};
}

template<typename T, size_t N>
class initialized_array: public std::array<T, N> {
public:
    template<typename... Args>
    initialized_array(Args &&... args)
        : std::array<T, N>(repeat<T>(std::make_index_sequence<N>(), std::forward<Args>(args)...)) {}
};

class DemoClass {
private:
    initialized_array<Data, CONSTANT> _member;
public:
    DemoClass():
        _member(1234)
    {
        // stuff
    }
};

Then your _member is statically allocated fixed size array. That approach is a bit complicated though, so maybe someone can provide a cleaner solution.

pptaszni
  • 5,591
  • 5
  • 27
  • 43
  • 1
    I've accepted this answer because it does answer my question as written. However, it really just tells me that I should just use a simpler solution, like finding a way to implement a default constructor on `Data`. Seeing all of these answers kind of made me realize why there's no simple one-line solution to initializing an array of objects without default constructors. – ItsAmy Jul 03 '19 at 15:04
2

A simple solution is to use std::vector. This obviously does has the downside of introducing dynamic allocation:

std::vector<Data> _member;

DemoClass() : _member(CONSTANT, Data(0))

It is possible to do the same without dynamic allocation, if you use a member of raw character storage (with sufficient size and alignment), and construct the elements with placement-new. That's what vector does also. It's a bit complicated however:

class DemoClass {
private:
    std::aligned_storage_t<sizeof(Data), alignof(Data)> storage[CONSTANT];
public:
    DemoClass()
    {
        std::uninitialized_fill(begin(), end(), Data(0));
    }

    Data* begin() {
        return std::launder(reinterpret_cast<Data*>(std::begin(storage)));
    }

    Data* end() {
        return std::launder(reinterpret_cast<Data*>(std::end(storage)));
    }

    ~DemoClass() {
        for(Data& d : *this)
            d.~Data();
    }
};
eerorika
  • 232,697
  • 12
  • 197
  • 326
1

The way you wrote the example code, it is not straightforward because you have created 2 constructors on Data.

However, If you change Data to the following (bear in mind: this is just to avoid any confusion as to which constructor gets called - you didn't provide the full code):

class Data {
public:
    Data(int number = 0){}
};

Now you can just brace-initalize with an empty brace:

_member{ }

This will make sure all the members are initialized.

Additional I would recommend using std::array<Data, CONSTANT> instead of the c array.

darune
  • 10,480
  • 2
  • 24
  • 62