Search code examples
c++c++11stdarray

Construct std::array by filling with one element


I have a list class in which the size variable is a const member. This is helpful to me because it enforces the requirement that the size of the list may vary from run to run, but can't vary within an individual run.

I would like to create a collection of these lists. The number of lists in the collection is a template variable, so I'd like to use a std::array... i.e., I'd like an array of lists, where the size of the array is a template parameter and the size of each list is a const specified at construction

Unfortunately:

  • The const-size list has no default constructor (its size needs to be specified!), so I need to supply a constructor argument to each element of the list. I can't just create the array and then set the elements
  • Because my list size is a template variable, I can't use a standard initializer list - the number elements required varies

I recognize that there are alternatives:

  • I could use a std::vector and just push_back the elements one by one until the size of the vector is equal to my template parameter, but this seems inelegant because it wouldn't naturally enforce the condition that the resulting vector's size shouldn't be changed after it's fully populated.
  • I could flip the index ordering, and have a const-sized list of std::arrays. This doesn't fit nicely with the rest of my code, however; I would like to be able to pass an individual const-sized list from the array to client code
  • I could create a default constructor for the const sized list class, create the array, and then use placement new to replace the array elements one-by-one. This seems like it could have some bad side-effects (what does const-sized list's default constructor do? What if it's called accidentally elsewhere? What happens when my successor has no idea what I've done?)

Since none of these are completely ideal, I'm thinking that it would be great if there were an array constructor (or helper function) which would take, as arguments:

  1. The number of elements in the array of T
  2. A single T object

...and return a std::array<T> where each T has been copy-constructed from argument 2.

Does such a thing exist?


Solution

  • Ok. Template magic. As std::array is an aggregate type, it can be initialized using aggregate initialization:

    std::array<T, 5> arr = { one, two, three, four, five };
    

    The idea is one, ..., five are five copies of a constructed object of type T (list in your case), using the user input as parameter. So, lets play. Don't laugh or cry after reading this:

    The idea is to take one object of type T, and replicate it five times:

     { T(param), .... }; // Five repetitions.
    

    So, lets create a function which returns the array already initialized:

    std::array<A, 5> arr = create_array_by_copy<5>(A(10));
    

    That function will return an array<A, 5> with 5 copies of that temporary object.

    For that, we will use an auxiliary struct which will create a parameter pack with a length of 5 (parametrized as s):

    template<std::size_t s, class... voids_t>
    struct sized_pack : public sized_pack<s - 1, voids_t..., void>
    {};
    
    template<class... voids_t>
    struct sized_pack<0, voids_t...>
    {};
    

    This will create a parameter pack, called voids_t, which is just a list of s voids. And now, the core of the trick:

    template<std::size_t s, class T, class... pack_t>
    std::array<T, s>
    create_array_by_copy_helper(sized_pack<0, pack_t...> const&,
                                T const& o)
    { return { (pack_t(), o)... }; }
    
    template<std::size_t s, class T>
    std::array<T, s> create_array_by_copy(T const& o)
    {
        return create_array_by_copy_helper<s>(sized_pack<s>(), o);
    }
    

    That's complicated. I know... as we have passed an object of type sized_pack<s> to the helper function, that temporary object will instantiate a hierarchy of sized_pack which last base class will be an object of type sized_pack<0, void, void, void, void, void>.

    The function apply will receive that object as a reference to size_pack<0, pack_t...> (the last base class, note the first 0), so, pack_t will be our list of 5 voids.

    Finally, we have:

     (pack_t(), o)
    

    which is just the comma operator, so, it returns o. The idea is that we have inserted pack_t (the parameter pack), inside the "pattern", so, when applying ... to the expression, it will be replaced by comma-separated expressions where each pack_t appearance will be replaced by each element in the parameter pack in the same order, so:

      { (pack_t(), o)... }
    

    is transformed to:

      { (void(), o), (void(), o), (void(), o), (void(), o), (void(), o) }
    

    an initialization list!! Finally, each element is just a void expressions followed by the coma operator and only the second element of each pair will be returned by the comma operator. So, the evaluted expression will be:

      return { o, o, o, o, o }; // With its corresponding calls to `forward`.
    

    Our desired initialization list!!

    Coliru example:

    http://coliru.stacked-crooked.com/a/d6c4ab6c9b203130

    You only need to replaced the type T with your list class.