The first thing to note is that std::initializer_list<Parent<T>>
will result in object slicing if some elements are initialized with Child<T>
because the underlying array would hold Parent<T>
objects.
There could be different solutions. For example, you could use a variadic template for the constructor to avoid slicing:
template<typename T>
struct Bar {
std::vector<std::unique_ptr<Parent<T>>> stuff;
template<typename... Us>
Bar(Us... us) {
stuff.reserve(sizeof...(Us));
(stuff.push_back(std::make_unique<Us>(std::move(us))), ...);
}
};
Here, (stuff.push_back(), ...)
is a fold-expression that will be expanded into a sequence of push_back
calls for each element of the us
pack. Due to the const-ness of the initializer_list
underlying array we can't do
Bar(Us... us) : stuff{std::make_unique<Us>(std::move(us))...}
{}
With C++17 deduction guides we can simplify the syntax a little bit.
template<typename> struct TypeTrait {};
template<typename T> struct TypeTrait<Parent<T>> { using Type = T; };
template<typename T> struct TypeTrait<Child<T>> { using Type = T; };
template<typename... Us>
Bar(Us...) -> Bar<std::common_type_t<typename TypeTrait<Us>::Type...>>;
Now we don't need to specify the template parameter explicitly, it will be deduced:
Parent<int> parent;
Child<int> child;
Bar bar{parent, child}; // T deduces to int
(If the member type Type
could be put into Parent
directly, a helper struct TypeTrait
is not needed.)