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

Tra*_*cer 20 c++ variadic-templates c++11

最近我问了这个问题,但现在我想扩大它.我写了以下课程:

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...);
    }
};
Run Code Online (Sandbox Code Playgroud)

使用时创建对象

X<int> obj(1, 2, 3);  // obj.v containts only 1
Run Code Online (Sandbox Code Playgroud)

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

Bri*_*ian 31

首先,你的代码不能为我编译.

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>
           ^
Run Code Online (Sandbox Code Playgroud)

我把外面的一个改成了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...);
    }
};
Run Code Online (Sandbox Code Playgroud)

你是正确的,这导致你在问题细节中给出的问题......

X<int> obj(1, 2, 3);  // obj.v containts only 1
Run Code Online (Sandbox Code Playgroud)

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

template <class T, class... T2>
X(T n, T2... rest): X(rest...) {
    v.insert(v.begin(), n);
}
Run Code Online (Sandbox Code Playgroud)

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

最好采取std::initializer_list<T>争论.这就是std::vector它自己的作用.

X(std::initializer_list<U> il): v(il) {}
// ...
X<int> obj {1, 2, 3};
Run Code Online (Sandbox Code Playgroud)


Aby*_*byx 5

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

template <class... E>
X(E&&... e) {
    int temp[] = {(v.push_back(e), 0)...};
}
Run Code Online (Sandbox Code Playgroud)

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

template <class... E>
X(E&&... e) {
    (void)std::initializer_list<int>{(v.push_back(std::forward<E>(e)), void(), 0)...};
}
Run Code Online (Sandbox Code Playgroud)

现场版

注意,下一个版本的 C++ 可能会有折叠表达式
(v.push_back(e), ...);


abi*_*gli 5

完全同意Brian的回答,但即使正确的方法是另一个(即使用initializer_list),请注意(为了在其他情况下正确地执行此操作),在这种情况下,可变模板递归可以通过注意到empty pack是一个有效的模板参数包,因此最后一次使用空包调用可变参数模板构造函数,这将导致尝试调用默认的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 ..}
};
Run Code Online (Sandbox Code Playgroud)

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