I want to build a container class for my Component
instances. Component is an abstract class and every type in the parameter pack should be derived from it.
template<typename... TComponents>
class ComponentContainer {
using ComponentPtr = unique_ptr<Component>;
public:
ComponentContainer()
{
}
private:
std::array<ComponentPtr, sizeof...(TComponents)> m_components;
};
The container should hold an array of pointers to the instances of each types provided.
So for every type there has to be m_components[i] = std::make_unique<TComponents>
().
TComponents
are derived from Component
type?How can I write it in code?
You could add a constructor that moves or copies the components supplied to your array using std::make_unique
:
#include <utility> // std::forward
template <typename... TComponents>
class ComponentContainer {
using ComponentPtr = std::unique_ptr<Component>;
public:
ComponentContainer() // default construct components
: m_components{std::make_unique<
std::remove_cv_t<std::remove_reference_t<TComponents>>>()...} {}
template <class... Args>
ComponentContainer(Args&&... comp) // move/copy construct components
: m_components{std::make_unique<
std::remove_cv_t<std::remove_reference_t<TComponents>>>(
std::forward<Args>(comp))...} {}
private:
std::array<ComponentPtr, sizeof...(TComponents)> m_components;
};
// deduction guide
template <class... Args>
ComponentContainer(Args...) ->
ComponentContainer<std::remove_cv_t<std::remove_reference_t<Args>>...>;
Making TComponents...
part of the type can be problematic though. If an element in the array was initialized as the derived type C1
, it will be odd if you later make the unique_ptr
point at a C2
. The template parameters and the contained object types won't match anymore.
Also, if you specify the template parameters explicitly, you may make them const
, but the container itself can't mix non-const
and const
Component
pointers, so they'll have to be non-const
.
For both those reasons, you should probably not let users get non-const
access to the individual unique_ptr
s directly.
You could add tuple
-like get
member functions to get a reference to the contained type with the correct const
qualification:
// helper trait to get the specific type out of a template parameter pack
template <size_t I, class... Args>
using type_in_pack_t = std::tuple_element_t<I, std::tuple<Args...>>;
// ...
template <std::size_t I>
const auto& get() const { // definite const&
return *static_cast<type_in_pack_t<I, TComponents...>*>(
m_components[I].get());
}
template <std::size_t I>
decltype(auto) get() { // non-const& / const& depending on template params
return *static_cast<type_in_pack_t<I, TComponents...>*>(
m_components[I].get());
}
It's often more useful to not make the concrete types of the components used to initialize the class template be a part of the final type. Since you store the elements in an array
, the size of the array could be the template parameter instead:
template <size_t N>
class ComponentContainer {
using ComponentPtr = std::unique_ptr<Component>;
public:
ComponentContainer() = default;
template <class... Args>
ComponentContainer(Args&&... comp) // move/copy construct components
: m_components{
std::make_unique<std::remove_cv_t<std::remove_reference_t<Args>>>(
std::forward<Args>(comp))...} {}
private:
std::array<ComponentPtr, N> m_components{};
};
// deduction guide
template <class... Args>
ComponentContainer(Args...) -> ComponentContainer<sizeof...(Args)>;
Is there a way to validate that all the provided
TComponents
are derived from Component type?
You'll get a compilation error if a pointer to one of the supplied TComponents
(after remove_const_t
) is not implicitly convertible to a Component*
.