Search code examples
c++c++17variadic-templatesunique-ptr

Capturing types from parameter pack and creating an array of unique_ptr from them


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>().

  1. How can I write it in code?
  2. Is there a way to validate that all the provided TComponents are derived from Component type?

Solution

  • 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>>...>;
    

    Demo

    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_ptrs 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)>;
    

    Demo

    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*.