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?

7
  • 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. Commented Jul 3, 2019 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? Commented Jul 3, 2019 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. Commented Jul 3, 2019 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. Commented Jul 3, 2019 at 13:03
  • 1
    You literally have Data() there, that's what default constructor is. EDIT: you had it there before you edited that away, anyway. Commented Jul 3, 2019 at 13:06

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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.
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();
    }
};

4 Comments

Data does not have a default constructor.
@ItsTimmy Oh. You should change the example accordingly.
Oh I see, I accidentally left that default constructor in from when I was testing. Sorry about that!
@ItsTimmy I re-wrote the answer.
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.