Jeśli o zmiennej liczbie argumentów argumentów znaczy elipsy (jak w void foo(...)
), a następnie te są mniej lub bardziej przestarzałe o zmiennej liczbie argumentów szablonów zamiast wykazy initializer - wciąż może być w niektórych przypadkach używać do elipsy podczas pracy z SFINAE do implementacji (na przykład) cech typu lub kompatybilności z C, ale będę mówić o zwykłych przypadkach użycia tutaj.
szablony o zmiennej liczbie argumentów, w rzeczywistości umożliwiają różne typy w opakowaniu argument (w rzeczywistości dowolny typu), natomiast wartości od An list inicjująca muszą być wymienialne na bazowego typu listy initalizer (i zwężenie konwersje są niedozwolone):
#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
}
z tego powodu, listy initializer są rzadziej stosowane, gdy wymagana jest typ odliczenie, o ile rodzaj argumentów jest rzeczywiście miało być jednorodna. Z kolei szablony Variadic zapewniają bezpieczną dla typu wersję dla listy argumentów variadic elips.
Ponadto wywołanie funkcji, która pobiera listę inicjalizacyjną, wymaga dołączenia argumentów w parze nawiasów klamrowych, co nie ma miejsca w przypadku funkcji przyjmującej pakiet argumentów z wariansem.
Wreszcie (cóż, istnieją inne różnice, ale te są bardziej istotne dla twojego pytania), wartości na listach inicjalizujących to const
obiektów. Na § 18.9/1 C++ 11 Standard:
obiektu typu initializer_list<E>
zapewnia dostęp do tablicy obiektów typu const E
. [...] Kopiowanie listy inicjalizatora nie powoduje skopiowania podstawowych elementów. [...]
Oznacza to, że chociaż typy, które nie mogą być kopiowane, mogą zostać przeniesione do list inicjalizujących, nie można ich przenieść z tego. To ograniczenie może, ale nie musi, spełniać wymagania programu, ale zazwyczaj sprawia, że inicjator zawiera listę ograniczających możliwości przechowywania typów nieobsługujących kopiowania.
Bardziej ogólnie, w każdym razie, gdy używamy obiektu jako elementu listy inicjalizacyjnej, albo zrobimy jego kopię (jeśli jest to l-wartość), albo odejdziemy od niego (jeśli jest to wartość rinalna):
#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
}
innymi słowy, listy inicjująca nie może być używany do przekazywania argumentów przez odniesienie (*) nie mówiąc już o wykonywaniu idealny do korespondencji:
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&&...)"
}
(*) należy jednak zauważyć, to initializer lists (unlike all other containers of the C++ Standard Library) do have reference semantics, więc chociaż kopiowanie/przenoszenie elementów jest wykonywane podczas wstawiania elementów do listy inicjalizatora, to kopiowanie samej listy inicjalizacyjnej nie spowoduje żadnej kopii/ruchu zawartych obiektów (jak wspomniano w paragrafie normy cytowanej powyżej) :
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
}
Listy inicjalizatorów mogą mieć tylko jeden typ. Należy pamiętać, że istnieją szablony variadyczne, w przeciwieństwie do nietypowych argumentów variadycznych w sejfie C. – chris
@chris: I to też szkoda. :( – GManNickG
@GManNickG, tak, 'std :: tuple' wbudowane w listy inicjalizatorów byłoby fajne, ale są niewątpliwie rzeczy, które byłyby trudne do wymyślenia, których po prostu nie mogę teraz wymyślić. – chris