В текущем состоянии С++ 11 (скажем, gcc 4.7.2), как мне выбрать между использованием varadic-template или std::initializer_list
, когда мне нужен конструктор, который может принимать переменные аргументы?
Как для конструкторов, как выбрать Variadic-templates vs std:: initializer_list?
Ответ 1
Variadic template позволяет вам предоставлять аргументы разных типов, а std::initializer_list
- шаблон с типом аргумента. Это означает, что тип всех элементов в списке должен быть одинаковым (или конвертируемым в базовый тип, но сужение конверсий не допускается). В зависимости от того, является ли это желательным для вас, вы можете выбрать тот или иной.
Кроме того, вариационный шаблон обычно является выбором по умолчанию, если вам нужна совершенная переадресация, поскольку синтаксическая форма T&&
может связываться как с ссылками lvalue, так и с ссылками rvalue, в то время как вычет подобного типа не может быть выполнен для 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
, будет вызываться по умолчанию, когда вы используете единый синтаксис инициализации (то есть фигурные скобки), хотя существует другой жизнеспособный конструктор. Это может быть или не быть тем, что вы хотите:
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
С вариационным шаблоном число аргументов известно во время компиляции (и доступно через sizeof...
). При значении std::initializer_list
количество аргументов известно только во время выполнения. Поэтому часть решения зависит от того, когда вам нужно или хотите узнать, сколько аргументов у вас есть.
Ответ 3
Я рекомендую всегда выбирать вариативные шаблоны и избегать std::initializer_list
, когда это возможно.
Вот как я бы реализовал std::vector
с С++ 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
}
Причина такова, как показано в main
, использование initializer_list
в общем коде может привести к разному поведению в зависимости от того, какой тип скобок был выбран. Существует также возможность тихо изменить код, добавив такой конструктор.
Другая причина - типы только для перемещения:
//std::vector<move_only> m1{move_only{}}; // won't compile
my_vector<move_only> m2{from_arglist, move_only{}}; // works fine