1

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?

user1476176
  • 1,045
  • 1
  • 7
  • 15
  • "the size of each list is a const specified at construction" - I sincerely hope you also mean at *compile-time*, right ? Otherwise you can toss the thought of using `std::array` from the get-go. – WhozCraig Apr 03 '16 at 05:35
  • The size of the lists is not a compile time constant. – user1476176 Apr 03 '16 at 05:46

2 Answers2

2

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.

ABu
  • 10,423
  • 6
  • 52
  • 103
  • Why are you reinventing `std::make_integer_sequence`? – T.C. Apr 03 '16 at 09:06
  • Ok, I didn't know it exists. Anyway, is a C++14 feature, and some compiler versions still used today (like g++ 4.8.4), hasn't full support for C++14. – ABu Apr 03 '16 at 12:57
  • 1
    Cool! I'm accepting this answer, but you might also be interested to see the answer someone else gave to a previous incarnation of my question: http://stackoverflow.com/questions/18497122/how-to-initialize-stdarrayt-n-elegantly-if-t-is-not-default-constructible – user1476176 Apr 03 '16 at 17:04
  • There was an error in my answer. The object must be passed by copy, because you can't move the same object five times. It is too dangerous. – ABu Apr 03 '16 at 17:29
  • And I have simplified the solution, though I know it could be simplified even further. – ABu Apr 03 '16 at 17:41
0

std::array<T, N> is a template class representing an array of length N of elements of type T. If you want to create array of lists you can just do std::array<std::list<T>>, N> where N is known at compile time. const std::size_t N is not enough, it has to be constexpr.

So no, you can't do that. You can however use std::vector for that.

If you post some code of your efforts, we can come up with something better.

Community
  • 1
  • 1
Zereges
  • 5,139
  • 1
  • 25
  • 49
  • Thanks! I'm thinking that maybe it's easiest to just store a std::array of pointers to the const-sized lists, then create the lists with the appropriate size using new(). I have to worry about new-ing and delete-ing, but it make the size guarantees that I'm after possible – user1476176 Apr 03 '16 at 06:35
  • @user1476176 Use smart pointer, it handles memory allocation/deallocation by itself. – Zereges Apr 03 '16 at 06:39