2013-03-17 6 views
27

Zastanawiam się, jakie są zalety argumentów variadic na listach inicjalizujących. Obie oferują tę samą zdolność - przekazywanie nieokreślonej liczby argumentów do funkcji.Po co używać argumentów variadic teraz, gdy dostępne są listy inicjalizatorów?

To, co osobiście uważam, to listy inicjalizacyjne są nieco bardziej eleganckie. Składnia jest mniej niezręczna.

Ponadto wydaje się, że listy inicjalizatorów mają znacznie lepszą wydajność, ponieważ liczba argumentów rośnie.

Więc czego mi brakuje, oprócz możliwości użycia również argumentów variadic w C?

+13

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

+0

@chris: I to też szkoda. :( – GManNickG

+0

@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

Odpowiedz

45

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 
} 
+1

To wspaniała odpowiedź! Tutaj dużo wiedzy, chłopaki. – Gui13

+0

@ Gui13: Dziękuję, w rzeczywistości będzie więcej do dodania, ale starałem się, aby był zwięzły na tyle, aby zawierał tylko punkty, które wydają mi się najbardziej istotne dla zadawanego pytania :) –

+0

Kolejna ważna różnica między szablonami variadic a Warianty w stylu C polegają na tym, że funkcja vararg w stylu C może być oddzielnie kompilowana. Szablon variadyczny nie może. –

1

skrócie C-style funkcji o zmiennej liczbie argumentów produkować mniej kodu gdy kompilowany niż C++ - typu zmiennej liczbie argumentów szablonów, więc jeśli jesteś zaniepokojony binarnym wielkości lub instrukcji ciśnienia cache, należy rozważyć wdrożenie swoją funkcjonalność z varargs zamiast jako szablonu.

Jednak szablony variadyczne są znacznie bezpieczniejsze i generują znacznie bardziej użyteczne komunikaty o błędach, więc często chcesz zawinąć swoją niedostępną funkcję variadic za pomocą wbudowanego szablonu variadic i umożliwić użytkownikom wywoływanie szablonu.

Powiązane problemy