Ответ 1
Если переменными аргументами вы понимаете эллипсы (как в void foo(...)
), то они становятся более или менее устаревшими variadic templates, а не списками инициализаторов - все еще могут быть некоторые варианты использования для эллипсов при работе с SFINAE для реализации (например) черт типа или для совместимости с C, но я расскажу об обычных случаях использования здесь.
Вариадические шаблоны, фактически, допускают разные типы для пакета аргументов (фактически, любого типа), в то время как значения списков инициализаторов должны быть конвертированы в базовый тип списка инициализаторов (и сужение конверсий не допускается)
#include <utility>
template<typename... Ts>
void foo(Ts...) { }
template<typename T>
void bar(std::initializer_list<T>) { }
int main()
{
foo("Hello World!", 3.14, 42); // OK
bar({"Hello World!", 3.14, 42}); // ERROR! Cannot deduce T
}
Из-за этого списки инициализаторов реже используются, когда требуется дедукция типа, если тип аргументов действительно не является однородным. С другой стороны, шаблоны Variadic предоставляют версию типа списка вариационных аргументов эллипсов.
Кроме того, вызов функции, которая принимает список инициализаторов, требует включения аргументов в пару фигурных скобок, что не относится к функции, принимающей пакет вариационных аргументов.
Наконец (ну, есть и другие отличия, но это те, которые более релевантны вашему вопросу), значения в списках инициализаторов являются объектами const
. В абзаце 18.9/1 стандарта С++ 11:
Объект типа
initializer_list<E>
обеспечивает доступ к массиву объектов типаconst E
. [...] Копирование списка инициализаторов не копировать базовые элементы. [...]
Это означает, что, хотя типы, не подлежащие копированию, могут быть перемещены в списки инициализаторов, они не могут быть удалены из него. Это ограничение может или не соответствовать требованиям к программе, но в целом делает списки инициализаций предельным выбором для хранения не копируемых типов.
В общем, во всяком случае, при использовании объекта в качестве элемента списка инициализаторов мы либо сделаем его копию (если это lvalue), либо удалим от него (если это rvalue):
#include <utility>
#include <iostream>
struct X
{
X() { }
X(X const &x) { std::cout << "X(const&)" << std::endl; }
X(X&&) { std::cout << "X(X&&)" << std::endl; }
};
void foo(std::initializer_list<X> const& l) { }
int main()
{
X x, y, z, w;
foo({x, y, z, std::move(w)}); // Will print "X(X const&)" three times
// and "X(X&&)" once
}
Другими словами, списки инициализаторов не могут использоваться для передачи аргументов по ссылке (*), не говоря уже о совершенной пересылке:
template<typename... Ts>
void bar(Ts&&... args)
{
std::cout << "bar(Ts&&...)" << std::endl;
// Possibly do perfect forwarding here and pass the
// arguments to another function...
}
int main()
{
X x, y, z, w;
bar(x, y, z, std::move(w)); // Will only print "bar(Ts&&...)"
}
(*) Следует, однако, отметить, что списки инициализаторов (в отличие от всех других контейнеров стандартной библиотеки С++) имеют ссылочную семантику, поэтому, хотя копия/перемещение элементов выполняется при вставке элементов в список инициализаторов, копирование самого списка инициализатора не приведет к копированию/перемещению содержащихся объектов (как указано в параграфе приведенного выше стандарта):
int main()
{
X x, y, z, w;
auto l1 = {x, y, z, std::move(w)}; // Will print "X(X const&)" three times
// and "X(X&&)" once
auto l2 = l1; // Will print nothing
}