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:
I recognize that there are alternatives:
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.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 codeSince 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:
...and return a std::array<T>
where each T
has been copy-constructed from argument 2.
Does such a thing exist?
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
void
s. 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 void
s.
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.