admin管理员组

文章数量:1399823

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.

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.

Share Improve this question asked Mar 24 at 23:26 Peter FerenczPeter Ferencz 763 silver badges8 bronze badges 3
  • Do you need the derived class? That is, could you write such a templated constructor for Layout? – JaMiT Commented Mar 25 at 1:09
  • 1 Layout(frames...) should be Layout{frames...}, otherwise the code looks correct. – NathanOliver Commented Mar 25 at 1:14
  • Layout constructor need one parameter of vector type, it must be called Layout({frames...}) or Layout{{frames...}}. – dalfaB Commented Mar 25 at 7:05
Add a comment  | 

1 Answer 1

Reset to default 1

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)...}) {}
};

本文标签: cCpp parameter pack in constructorStack Overflow