2

Let's say I have a class without a default constructor called Foo.

If I were using an std::vector, I could do this:

std::vector<Foo> vec(100, Foo(5));

This would create a vector of 100 elements, each with value Foo(5).

How do I do the same with std::array<Foo, 100>?

I obviously do not want to list out Foo(5) explicitly 100 times in an initializer list. And yet I cannot wait till after the array is constructed to initialize it, since the lack of default constructor will produce a compiler error.

Bonus points for a solution that allows me to avoid the copy constructor as well, by supplying explicit constructor arguments similar to "placement new" or emplace functions.

3
  • Your vector example does use copy constructor. It seems a bit unfair to hold std::array to a higher standard. Commented Feb 7, 2022 at 5:14
  • There's nothing "unfair" about it. I just want to find out how to do this. If I were actually using a vector, I could avoid the copy constructor in other ways. But that is optional anyway. Commented Feb 7, 2022 at 5:22
  • I figured out how to do it without copying, after all. See my updated answer. Commented Feb 7, 2022 at 15:24

2 Answers 2

7

With copy constructor, something along these lines:

template <typename T, size_t... Is>
std::array<T, sizeof...(Is)> MakeArrayHelper(
    const T& val, std::index_sequence<Is...>) {
  return {(static_cast<void>(Is), val) ...};
}

template <typename T, size_t N>
std::array<T, N> MakeArray(const T& val) {
  return MakeArrayHelper<T>(val, std::make_index_sequence<N>{});
}

std::array<Foo, 100> arr = MakeArray<Foo, 100>(Foo(5));

Actually, this can be done without copy constructor after all. This solution relies heavily on C++17's mandatory copy elision.

template <typename T, size_t... Is, typename... Args>
std::array<T, sizeof...(Is)> MakeArrayHelper(
    std::index_sequence<Is...>, Args&&... args) {
  return {(static_cast<void>(Is), T{std::forward<Args>(args)...}) ...};
}

template <typename T, size_t N, typename... Args>
std::array<T, N> MakeArray(Args&&... args) {
  return MakeArrayHelper<T>(std::make_index_sequence<N>{},
                            std::forward<Args>(args)...);
}

Demo

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

11 Comments

Thanks. It's ugly, but it could do for now. And probably better than a preprocessor macro.
I think this (Is, val), ... should be (Is, val).... And std::array<Foo, 100> arr... should be auto arr = MakeArray<foo_return_type, 100>(Foo(5)).
@zdf Fixed the typo, the comma indeed shouldn't be there. Foo is meant to be a type, not a function, so foo_return_type doesn't make sense.
@Matt I figured out how to do it without copying. Updated the answer.
(Is, val) -> (static_cast<void>(Is), val) to handle evil overloaded operator comma.
|
2

std::array is just a thin wrapper around a fixed array. It has no repeat-insert logic, like std::vector does. Since Foo does not have a default constructor, the only way to initialize an instance of std::array<Foo, N> is to use aggregate initialization with N number of values specified (sorry, I know you don't want to to do this), eg:

std::array<Foo, 100> arr{5, 5, 5, ...}; // N times...

Otherwise, you will have to create a byte array of sufficient size and then use placement-new in a loop, eg:

std::aligned_storage_t<sizeof(Foo), alignof(Foo)> arr[100];
for(int i = 0; i < 100; ++i) {
   new (&arr[i]) Foo(5);
}
...
// use static_cast<Foo*>(&arr[index]) to access each object as needed...
...
for(int i = 0; i < 100; ++i) {
    // when using placement-new, you must call each object's destructor explicitly...
    static_cast<Foo*>(&arr[i])->~Foo();
}

4 Comments

Is there some alternative to std::array that could do this? The manual byte array would work, but it wouldn't provide the nice vector-like interface with all modern C++ goodies. I could roll my own of course, but I was hoping it wouldn't be necessary.
placement new way is very dangerous, as copy/move has also to be handled... :/
Danger is in the eyes of the beholder. People who are used to programming in C and old school C++ would have nothing to fear. Yet, it is yet another case of "why do I have to roll my own?"
Minor detail, aligned_storage and crew it's recommended to do it "more manually" with alignas and byte arrays ( open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1413r3.pdf ) . I am facing a similar issue and working on an "uninitialized array" in my own estdlib for embedded

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.