c++templatesparameter-pack

Cpp parameter pack in constructor


So I have a class called Layout with the following code:

class Layout(){
protected:
   std::vector<Frame*> frames;
public:
   Layout(std::vector<Frame*> frames = {}) : frames(frames) {}
};

I am trying to achieve a high-level behavior where I can create layouts that inherit from the base layout class:

class CustomLayout : public Layout{
   //I am missing the correct constructor template here
};

So when I call the constructor, it populates the frames vector with the constructor arguments

CustomLayout* layout = new CustomLayout(
   new Frame1(),
   new Frame2(),
   ...
);

I wish to achieve this with template code:

template<typename... T>
CustomLayout::CustomLayout(T... frames)
: Layout(frames...) {
    
}

Any help is appreciated.


Solution

  • There are several solutions that can solve your problem.

    The solution 1 and solution 2 below are alternative solutions without parameter packs. And the solution 3 is the solution based on your idea to define a constructor with a parameter pack.

    Why do I present the first two solutions? Because I don't think define a constructor with a parameter-pack to do this job is a good idea. Since a "layout" is a container of multiple frames logically, as STL containers and the C-style array do, this is the responsiblility of braced-init-lists. The parameter-pack should be used as something like emplace. Furthermore, the parameter-pack has no constrains on the types, although we know they should all be convertible to Frame*.

    Since the parameter of the constructor of Layout is an std::vector<Frame>, and the arguments you pass are elements of the std::vector<Frame>, our goal is to call the constructor of std::vector<Frame> the parameters of std::initializer_list<Frame>.

    Solution 1: Inherite the constructor of the base class explicitly

    The first solution is to give CustomLayout a constructor with a parameter of std::vector<Frame>, so we can call the constructor of std::vector<Frame> directly when passing arguments. The simplest way is to inherit the constructor of Layout:

    class CustomLayout : public Layout {
    public:
       using Layout::Layout;
    };
    

    or you can define it manually:

    class CustomLayout : public Layout{
    public:
       CustomLayout(const std::vector<Frame*>& frames) : Layout(frames) {}
       CustomLayout(std::vector<Frame*>&& frames) : Layout(std::move(frames)) {}
    };
    

    then when constructing an object of type CustomLayout, we can use List-initialization to construct a temporary object of type std::vector<Frame*> when passing arguments:

    CustomLayout* layout = new CustomLayout({
        new Frame1(),
        new Frame2(),
    });
    

    Note that in ({...}), the outer parentheses (...) is to call the constructor with parameter std::vector<Frame*>. And the inner braces {...} is a braced-init-list, which will match the initializer-list constructor of std::vector<Frame*>.

    By the way, ({...}) and {{...}} are both correct. Because in the latter manner, the outer braces {...} can also match ordinary constructors since CustomLayout has no initializer-list constructors.

    Solution 2: Define an initializer-list constructor for CustomLayout

    You can also define an initializer-list constructor for CustomLayout, so that you can use List-initialization to initialize the CustomLayout object directly.

    class CustomLayout : public Layout{
    public:
       CustomLayout(std::initializer_list<Frame*> frames) : Layout(frames) {}
    };
    

    And then you can construct an object of type CustomLayout directly with {...}:

    CustomLayout* layout = new CustomLayout{
        new Frame1(),
        new Frame2(),
    };
    

    And the braces {...} as a braced-init-list will match the initializer-list constructor of CustomLayout.

    Solution 3: Define an constructor with a parameter-pack

    Based on the idea of the questioner, if we define a constructor with a parameter-pack, we should use the parameters (whose types are template parameters) as arguments of the std::vector<Frame*>, which is the parameter of the constructor of the base class object Layout. This is the same as solution 1. So we should use ({...}) or {{...}} to do that (the reason is presented in solution 1):

    class CustomLayout : public Layout {
    public:
        template <typename... T>
        CustomLayout(T... frames) : Layout({frames...}) {}
    };
    

    Then you can call it as:

    CustomLayout* layout = new CustomLayout(
        new Frame1(),
        new Frame2()  // No trailing commma ^_^! Too bad I think :(
    );
    

    By the way, in this senario, the parameters are all pointers, which are all scalar types, so there's no extra overhead when passing them by value. Generally, we should use std::forward to avoid extra copies, if some of them are not trivially copiable, or even not copiable (if you wanna use std::unique_ptr<Frame>) ^_^:

    class CustomLayout : public Layout {
    public:
        template <typename... T>
        CustomLayout(T&&... frames) : Layout({std::forward<T>(frames)...}) {}
    };