11

在 c++11 的当前状态(比如 gcc 4.7.2),我应该如何选择使用可变参数模板std::initializer_list当我需要一个可以接受可变参数的构造函数时?

4

3 回答 3

15

可变参数模板允许您提供不同类型的参数,而 anstd::initializer_list模板使用参数的类型。这意味着列表中所有元素的类型必须相同(或可转换为基础类型,但不允许缩小转换)。根据您是否需要这样做,您可以选择其中一个。

此外,如果您需要完美转发,可变参数模板通常是默认选择,因为语法形式T&&可以绑定到左值引用和右值引用,而不能对 执行类似的类型推导initializer_list

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

另请注意,initializer_list当您使用统一初始化语法(即花括号)时,默认情况下会调用接受 an 的构造函数,即使存在另一个可行的构造函数。这可能是也可能不是您希望拥有的东西:

struct A
{
    A(int i) { }
};

struct B
{
    B(int) { }
    B(std::initializer_list<A>) { }
};

int main()
{
    B b {1}; // Will invoke the constructor accepting initializer_list
}
于 2013-02-16T19:29:27.153 回答
4

使用可变参数模板,参数的数量在编译期间是已知的(并且可以通过 访问sizeof...)。使用 a std::initializer_list,参数的数量仅在运行时才知道。因此,部分决定取决于您何时需要或想知道您有多少参数。

于 2013-02-16T20:39:12.457 回答
3

我建议始终选择可变参数模板并std::initializer_list尽可能避免。

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

#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
}

原因如 中所示maininitializer_list在通用代码中使用可能会导致不同的行为,具体取决于选择的括号类型。还可以通过添加这样的构造函数来静默更改代码。

另一个原因是只移动类型:

//std::vector<move_only> m1{move_only{}}; // won't compile
my_vector<move_only> m2{from_arglist, move_only{}}; // works fine
于 2013-02-16T19:38:11.800 回答