88

Is the absence of

std::array<T,size>::array(const T& value);

an oversight? It seems mighty useful to me, and dynamic containers (like std::vector) do have a similar constructor.

I am fully aware of

std::array<T,size>::fill(const T& value);

but that is not a constructor, and the memory will be zeroed out first. What if I want all -1's like this guy?

Community
  • 1
  • 1
rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • 1
    "and the memory will be zeroed out first" are you sure this is true? – tohava Jul 29 '13 at 12:14
  • 3
    It won't be zeroed out first, unless you ask for it. – R. Martinho Fernandes Jul 29 '13 at 12:14
  • 1
    Besides the *aggregate*-argument from all the answers, there could also be a more conceptual reasoning. A fill-constructor would probably hide the fact that it isn't really constructing the individual elements. It will first and foremost invoke the aggregate initialization and *then* copy the value into the elements, it can't copy-construct the elements right away (in contrast to, say a `std::vector`). So since it would always be equivalent to `array(); array.fill();`, omitting the constructor in the first place doesn't hide this fact. – Christian Rau Jul 29 '13 at 12:38
  • 1
    Also relevant: http://stackoverflow.com/questions/18497122/how-to-initialize-stdarrayt-n-elegantly-if-t-is-not-default-constructible – kennytm Sep 02 '13 at 15:52

5 Answers5

59

std::array is, by design, an aggregate, so has no user-declared constructors.

As you say, you could use fill after default constructing. Since it's an aggregate, default construction won't zero the memory, but will leave it uninitialised (if the contained type is trivially initialisable).

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 1
    So [this page](http://en.cppreference.com/w/cpp/container/array) is wrong? It says explicitly that the array's elements are default-initialized. – rubenvb Jul 29 '13 at 12:15
  • 15
    Default-initialization is no-init for PODs, and default-constructor for everything else I believe - depending on the point of declaration. – Xeo Jul 29 '13 at 12:15
  • 1
    @rubenvb: Yes, they'll be default-initialised, not value-initialised. So if they are trivially initialisable, then they'll be left uninitialied. – Mike Seymour Jul 29 '13 at 12:15
  • Ah yes. That still remains a very naughty part of the standard IMO which draws a distinction between user and built-in types `:/` – rubenvb Jul 29 '13 at 12:20
  • 11
    @rubenvb The primitive types all have trivial default-initialization. User-defined types can behave in the same way if desired. That's not distinction, it's consistency. – Casey Jul 29 '13 at 13:02
25

Note that you can efficiently simulate this type of constructor by taking advantage of the fact that array is not zero-initialized, and has a copy constructor and do.

template <size_t N, class T>
array<T,N> make_array(const T &v) {
    array<T,N> ret;
    ret.fill(v);
    return ret;
}

auto a = make_array<20>('z');
tohava
  • 5,344
  • 1
  • 25
  • 47
  • let's not bloat the call site with unneeded template parameters `;-)`. – rubenvb Jul 29 '13 at 12:18
  • 4
    `char` can be inferred, so you can just write `make_array<20>('z')` instead of `make_array<20,char>('z')` – Nawaz Jul 29 '13 at 12:19
  • @Nawaz oh, even better. I should have asked why there is no `make_array` instead `:-)` – rubenvb Jul 29 '13 at 12:20
  • Is return-value optimisation guaranteed here? Or is it better to return a rvalue reference? – Walter Sep 02 '13 at 16:58
  • 1
    @Walter: You couldn't return a reference of any sort, because you'd return a reference to a local variable. – GManNickG Sep 02 '13 at 17:07
  • Arrays larger than `2^31` on systems with 32 bit ints are not supported :( – Yakk - Adam Nevraumont Feb 24 '14 at 19:39
  • @Walter: Yes return-value optimization is guaranteed when the variable is constructed in the same scope and then returned by copy. The compiler should automatically use move semantics since C++11/14. – satanik May 08 '15 at 18:31
  • 4
    This isn't going to work when `T` is not default constructible, and that's when you need the fill constructor badly. – Chris Beck Jun 28 '18 at 19:15
  • @ChrisBeck, yes, see here: https://stackoverflow.com/a/72760156/225186 – alfC Jan 30 '23 at 03:54
15

You may use std::index sequence for that:

namespace detail
{

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

template <std::size_t N, typename T>
constexpr std::array<T, N> make_array(const T& value)
{
    return detail::make_array(value, std::make_index_sequence<N>());
}

Demo

std::make_index_sequence is C++14, but can be implemented in C++11.

static_cast<void>(Is) is to handle evil operator, that T might provide.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 4
    This is the most useful answer, it works also when `T` is not default constructible, which is when the other answers fall short. – Chris Beck Jun 28 '18 at 19:14
  • 1
    @ChrisBeck Could not agree more. That was precisely the case for me and I was getting frustrated with the other answers. Goes to show that up-votes ain't everything. – Jonas Greitemann Sep 02 '18 at 15:22
  • Because of using template recursion, this won't compile for large arrays. If I need a million-element array of T with no default constructor am I out of luck? Even a C array would work for me. – Michał Brzozowski Feb 11 '19 at 04:26
  • @MichałBrzozowski: `std::index_sequence` might be implemented with logarithm instantiation instead of linear. and compiler might have *"intrinsics"* to do only one instantiation. Then, there are no longer recursion. – Jarod42 Feb 11 '19 at 09:09
  • That still doesn't work very well, because the resulting binary size is linear with respect to the size of the array - there is a dedicated piece of code for initializing each element in the array. In the usual case when elements are default constructed, this is done in a runtime loop. – Michał Brzozowski Feb 12 '19 at 03:04
  • 1
    @MichałBrzozowski: You can still switch to `std::vector`, and `reserve` whole size and `emplace_back` in a loop.As for a million-element, stack would be problematic as memory limited in practice. – Jarod42 Feb 12 '19 at 09:14
10

First of all, it is not std::array<T>, it is std::array<T,N> where N is compile time constant integral expression.

Second, std::array is made aggregate by design. So it doesn't have anything which makes it non-aggregate, which is why it doesn't have constructor... and destructor, virtual functions, etc.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
1

I took answer from Jarod42 and made an extension to be able to use variable amount of Constructor arguments and also added automatic indexer as a first arg:

namespace detail {
  template <typename T, std::size_t... Seq, typename... Args>
  constexpr std::array<T, sizeof...(Seq)> make_array(std::index_sequence<Seq...>, Args &... args)
  {
    return {{(static_cast<void>(Seq), T(Seq, args...))...}};
  }
}  // namespace detail

template <typename T, std::size_t N, typename... Args>
constexpr std::array<T, N> make_array(Args &... args)
{
  return detail::make_array<T>(std::make_index_sequence<N>(), args...);
}

class myClass {
  myClass(unsigned int i, float a, std::string b, int c):... {};
}

Usage:

auto myArray = make_array<myClass, 64>(a, b, c);