For constructors, how do I choose between variadic-templates vs std::initializer_list?

╄→гoц情女王★ 提交于 2019-12-04 18:05:25

问题


In the current state of c++11 (say gcc 4.7.2), how should I choose between using a variadic-template or a std::initializer_list when I need a constructor that can take variable arguments?


回答1:


A variadic template allows you providing arguments of different types, while an std::initializer_list is templated with the type of the argument. This means the type of all the elements in the list must be the same (or convertible to the underlying type, but no narrowing conversions are allowed). Depending on whether or not this is desirable for you, you may choose one or the other.

Also, a variadic template is usually the default choice if you need perfect forwarding, in that the syntactic form T&& can bind to both lvalue references and rvalue references, while a similar type deduction cannot be performed for 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>&&) { }
};

Also notice, that a constructor accepting an initializer_list will be invoked by default when you use uniform initialization syntax (i.e. curly braces), even though another viable constructor exists. This may or may not be something you wish to have:

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
}



回答2:


With a variadic template, the number of arguments is known during compilation (and accessible via sizeof...). With a std::initializer_list, the number of arguments is known only at runtime. So part of the decision depends on when you need or want to know how many arguments you have.




回答3:


I recommend always chosing variadic templates and avoid std::initializer_list whenever possible.

This is how I would have implemented std::vector with 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
}

The reason is as showed in main, using initializer_list in generic code can lead to different behaviour depending on which type of parentheses was chosen. There is also the possibility to silently change code by adding such an constructor.

Another reason are move-only types:

//std::vector<move_only> m1{move_only{}}; // won't compile
my_vector<move_only> m2{from_arglist, move_only{}}; // works fine


来源:https://stackoverflow.com/questions/14914200/for-constructors-how-do-i-choose-between-variadic-templates-vs-stdinitializer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!