编写可变参数模板构造函数

Posted

技术标签:

【中文标题】编写可变参数模板构造函数【英文标题】:Writing variadic template constructor 【发布时间】:2015-05-06 03:44:19 【问题描述】:

最近我问了this 的问题,但现在我想扩展它。我写了以下课程:

template <class T>
class X
public:
    vector<T> v;
    template <class T>
    X(T n) 
        v.push_back(n);
    
    template <class T, class... T2>
    X(T n, T2... rest) 
        v.push_back(n);
        X(rest...);
    
;

使用

创建对象时
X<int> obj(1, 2, 3);  // obj.v containts only 1

Vector 只包含第一个值,而不包含其他值。我检查并看到构造函数被调用了 3 次,所以我可能正在创建临时对象并用其余参数填充它们的向量。我该如何解决这个问题?

【问题讨论】:

为什么不只是template &lt;typename ...Args&gt; X(Args ...args) : v(args...) 您的类确实应该采用std::initializer_list&lt;T&gt; 参数,而不是使用可变参数模板构造函数。这是惯用的。 我是 C++11 的新手,所以还在学习。 【参考方案1】:

首先,您的代码无法为我编译。

main.cpp:7:15: error: declaration of ‘class T’
     template <class T>
               ^
main.cpp:3:11: error:  shadows template parm ‘class T’
 template <class T>
           ^

我把外面的改成了U

template <class U>
class X
public:
    vector<U> v;
    template <class T>
    X(T n) 
        v.push_back(n);
    
    template <class T, class... T2>
    X(T n, T2... rest) 
        v.push_back(n);
        X(rest...);
    
;

您是正确的,这会导致您在问题详细信息中给出的问题...

X<int> obj(1, 2, 3);  // obj.v containts only 1

这是因为构造函数末尾的语句X(rest...) 不会递归调用构造函数来继续初始化同一个对象;它会创建一个 new X 对象,然后将其丢弃。一旦构造函数的 body 开始执行,就不能再调用同一个对象上的另一个构造函数。委托必须发生在 ctor-initializer 中。例如,您可以这样做:

template <class T, class... T2>
X(T n, T2... rest): X(rest...) 
    v.insert(v.begin(), n);

这很糟糕,因为在向量的开头插入效率不高。

最好采用std::initializer_list&lt;T&gt; 参数。这就是std::vector 本身所做的。

X(std::initializer_list<U> il): v(il) 
// ...
X<int> obj 1, 2, 3;

【讨论】:

奇怪。我没有编译错误(VS 2013)。但是,是的,我明白问题出在哪里。谢谢。【参考方案2】:

完全同意 Brian 的回答,但即使正确的方法是另一种方法(即使用 initializer_list),请注意(为了在其他情况下正确执行此操作)在这种情况下,可变参数模板递归可能会简单得多注意空包是一个有效的模板参数包,因此可变参数模板构造函数将与空包一起最后一次调用,这将导致尝试调用默认 ctor,它可以被默认并终止递归。

IOW,以下方法会起作用,而且 IMO 会更干净:

template <class T>
struct X

    std::vector<T> v;
    X() = default; //Terminating recursion

    template <class U, class... Ts>
    X(U n, Ts... rest)  : X(rest...)  .. the recursive work ..
;

再次,我并不是说模板和递归在这里是正确的,我只是指出它们是否必要,会有一种更简单的方法来使用它们。

【讨论】:

【参考方案3】:

首先不需要递归 - 您可以使用“临时数组”习语并编写

template <class... E>
X(E&&... e) 
    int temp[] = (v.push_back(e), 0)...;

这种方法的正确(更复杂)版本如下所示:

template <class... E>
X(E&&... e) 
    (void)std::initializer_list<int>(v.push_back(std::forward<E>(e)), void(), 0)...;

Live version

注意,下一个版本的 C++ 可能会有 Fold Expressions(v.push_back(e), ...);

【讨论】:

【参考方案4】:
template <class T, class... Rest>
  X(T n, Rest... rest) 
   add(rest...);
 

//Just another member function
template<class T>
  void add(T n) 
    v.push_back(n);
 
  template<class T, class ... Rest>
   void add(T n, Rest... rest) 
     v.push_back(n);
     add(rest...);

 

【讨论】:

欢迎来到 ***。虽然这可能会回答问题,但也请简要说明您的代码实际做了什么以及它如何解决最初的问题。

以上是关于编写可变参数模板构造函数的主要内容,如果未能解决你的问题,请参考以下文章

如何通过可变参数模板将多个构造函数参数转发到数组初始值设定项列表?

可变参数模板

可变参数模板模板和完美转发

使用可变参数模板函数围绕类实现基于 pImpl 的包装器

C++中的可变参数模板

可变参数模板和通过赋值的复制构造