C++ 对于构造函数,如何在可变模板与std::initializer\u列表之间进行选择?

C++ 对于构造函数,如何在可变模板与std::initializer\u列表之间进行选择?,c++,templates,c++11,variadic-templates,initializer-list,C++,Templates,C++11,Variadic Templates,Initializer List,在c++11的当前状态下(比如gcc 4.7.2),当我需要一个可以接受变量参数的构造函数时,我应该如何选择使用可变模板还是使用std::initializer\u list,而std::initializer_list是用参数类型模板化的。这意味着列表中所有元素的类型必须相同(或可转换为基础类型,但不允许缩小转换)。取决于这是否适合您,您可以选择其中之一 此外,如果需要完美的转发,可变模板通常是默认选择,因为语法形式T&可以绑定到左值引用和右值引用,而不能对初始值设定项列表执行类似的类型推断:

在c++11的当前状态下(比如gcc 4.7.2),当我需要一个可以接受变量参数的构造函数时,我应该如何选择使用可变模板还是使用
std::initializer\u list
,而
std::initializer_list
是用参数类型模板化的。这意味着列表中所有元素的类型必须相同(或可转换为基础类型,但不允许缩小转换)。取决于这是否适合您,您可以选择其中之一

此外,如果需要完美的转发,可变模板通常是默认选择,因为语法形式
T&
可以绑定到左值引用和右值引用,而不能对
初始值设定项列表执行类似的类型推断:

struct A
{
    // Deduces T& for lvalue references, T for rvalue references, and binds to both
    template<typename... Ts>
    A(Ts&&...) { }

    // This is an rvalue reference to an initializer_list. The above type deduction
    // does not apply here
    template<typename T>
    A(initializer_list<T>&&) { }
};
我建议始终选择可变模板,尽可能避免使用
std::initializer\u list

这就是我用C++11实现std::vector的方法:

#include <iostream>
#include <vector>

struct exp_sequence {
  template <typename... T>
  exp_sequence(T&&...) {}
};

struct from_arglist_t {} from_arglist;

template <typename T>
class my_vector {
  std::vector<T> data;

public:
  my_vector(int n, T const& e) : data(n, e) {}

  template <typename... Args>
  my_vector(from_arglist_t, Args&&... args) {
    data.reserve(sizeof...(Args));
    exp_sequence{(data.push_back(std::forward<Args>(args)),1)...};
  }

  std::size_t size() { return data.size(); }
};

int main()
{
  std::vector<int> v1{13, 13}; std::cout << v1.size() << '\n'; // 2
  std::vector<int> v2(13, 13); std::cout << v2.size() << '\n'; // 13

  my_vector<int> v3{13, 13}; std::cout << v3.size() << '\n'; // 13
  my_vector<int> v4(13, 13); std::cout << v4.size() << '\n'; // 13
  my_vector<int> v5(from_arglist, 13, 13); std::cout << v5.size() << '\n'; // 2
  my_vector<int> v6{from_arglist, 13, 13}; std::cout << v6.size() << '\n'; // 2
}

对于可变模板,参数的数量在编译过程中是已知的(可通过
sizeof…
访问)。对于
std::initializer_list
,参数的数量只有在运行时才知道。因此,部分决定取决于您何时需要或想要知道您有多少个参数。

我不确定(这就是为什么这是一个注释),但变量模板不能处理不同的类型,而初始值设定项列表必须是相同的类型?@JoachimPileborg,绝对正确,尽管您可以在
int..
std::initializer\u list
之间进行选择。我说,选择感觉更自然的一个。扩展到完美转发的部分,
std::initializer\u list::reference
(这是例如
*l.begin()
)的结果类型是
T const&
,它禁止移动。您可以只将值移入,但不能将它们移出。
exp\u序列
技巧很巧妙,但它确实保证按正确的顺序插入元素吗?AfAIR、C和C++从未承诺以词汇顺序来评估函数参数。因此,除非在C++11标准中对可变模板构造函数的参数求值顺序有特殊保证,否则这是不可移植的。@fgp:参数求值顺序确实保证从左到右排序。这适用于大括号初始化的每次使用(因此exp_序列需要是一个类)。@ipc在
exp_序列{(data.push_back(std::forward(args)),1)…}
上到底发生了什么?为什么我们需要包围
数据。通过
,1)
向后推(std::forward(args))
?为什么
1
?容器具有编译时大小,因为它必须在完整状态下初始化(不能向后推等)。如果您的意思是它的
::size()
方法没有被标记为
constepr
,那么这是C++11中的一个缺陷,在C++14中已修复。因此,您可以根据命名的
初始值设定项列表的大小设置模板。在许多方面,该大小在编译时可用。但当然,可能没有可变选择那么多方式!作为
初始值设定项\u list
的构造函数参数在编译期间没有已知的大小,因为可以从多个站点调用构造函数,每个站点的初始值设定项大小不同。
#include <iostream>
#include <vector>

struct exp_sequence {
  template <typename... T>
  exp_sequence(T&&...) {}
};

struct from_arglist_t {} from_arglist;

template <typename T>
class my_vector {
  std::vector<T> data;

public:
  my_vector(int n, T const& e) : data(n, e) {}

  template <typename... Args>
  my_vector(from_arglist_t, Args&&... args) {
    data.reserve(sizeof...(Args));
    exp_sequence{(data.push_back(std::forward<Args>(args)),1)...};
  }

  std::size_t size() { return data.size(); }
};

int main()
{
  std::vector<int> v1{13, 13}; std::cout << v1.size() << '\n'; // 2
  std::vector<int> v2(13, 13); std::cout << v2.size() << '\n'; // 13

  my_vector<int> v3{13, 13}; std::cout << v3.size() << '\n'; // 13
  my_vector<int> v4(13, 13); std::cout << v4.size() << '\n'; // 13
  my_vector<int> v5(from_arglist, 13, 13); std::cout << v5.size() << '\n'; // 2
  my_vector<int> v6{from_arglist, 13, 13}; std::cout << v6.size() << '\n'; // 2
}
//std::vector<move_only> m1{move_only{}}; // won't compile
my_vector<move_only> m2{from_arglist, move_only{}}; // works fine