2013-02-16 6 views
10

Nello stato corrente di C++ 11 (ad esempio gcc 4.7.2), come dovrei scegliere tra l'uso di un modello variadic o un std::initializer_list quando ho bisogno di un costruttore che può accettare argomenti variabili?Per i costruttori, come scegliere tra variadic-templates vs std :: initializer_list?

+3

Non sono sicuro (motivo per cui questo è un commento) ma i modelli variadic non possono gestire tipi diversi mentre un elenco di inizializzazione deve essere dello stesso tipo? –

+0

@JoachimPileborg, assolutamente corretto, sebbene potessi scegliere tra 'int ...' e 'std :: initializer_list '. Dico scegli ciò che sembra più naturale da usare. – chris

risposta

15

Un modello variadic consente di fornire argomenti di tipi diversi, mentre un modello std::initializer_list è basato sul tipo dell'argomento. Ciò significa che il tipo di tutti gli elementi nell'elenco deve essere uguale (o convertibile al tipo sottostante, ma non sono consentite conversioni di restringimento). A seconda che sia desiderabile o meno per te, puoi scegliere l'uno o l'altro.

Inoltre, un modello variadic è di solito la scelta di default, se avete bisogno perfetto inoltro, in quanto la forma sintattica T&& può legarsi ad entrambi i riferimenti lvalue e riferimenti rvalue, mentre un tipo simile deduzione non può essere eseguita per 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>&&) { } 
}; 

si noti inoltre, che un costruttore che accetta un initializer_list verrà richiamato per impostazione predefinita quando si utilizza la sintassi di inizializzazione uniforme (ad esempio parentesi graffe), anche se esiste un altro costruttore praticabile. Questo può o non può essere qualcosa che si desidera avere:

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

Espansione sul bit dell'inoltro perfetto, 'std :: initializer_list :: reference' (che è il tipo di risultato di ad esempio' * l.begin() ') è' T const &', che vieta le mosse. Puoi inserire valori di solo spostamento in, ma non puoi spostarli. –

+0

@LucDanton: buon punto. –

3

vi consiglio sempre scegliendo modelli variadic ed evitare std::initializer_list quando possibile.

Questo è come avrei implementato std::vector con 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 
} 

La ragione è come mostrato in main, utilizzando initializer_list in codice generico può portare ad un comportamento diverso a seconda del tipo di parentesi era scelto. C'è anche la possibilità di cambiare codice in modo silenzioso aggiungendo un simile costruttore.

Un altro motivo sono mossa sola tipi:

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

Il trucco 'exp_sequence' è pulito, ma in realtà garantisce di inserire gli elementi nell'ordine corretto? AFAIR, C e C++ non hanno mai promesso di valutare gli argomenti della funzione in ordine lessicale. Quindi, a meno che non ci sia una garanzia speciale nello standard C++ 11 sull'ordine di valutazione degli argomenti per costruttori di template variadici, questo non è portabile. – fgp

+1

@fgp: l'ordine di valutazione degli argomenti è infatti garantito per essere sequenziato da sinistra a destra.Ciò vale per ogni utilizzo dell'inizializzazione delle parentesi graffe (quindi exp_sequence deve essere una classe). – ipc

+1

@ipc Che cosa sta succedendo esattamente in exp_sequence {(data.push_back (std :: forward (args)), 1) ...} '? Perché abbiamo bisogno di racchiudere 'data.push_back (std :: forward (args))' per '(' e ', 1)'? E perché '1'? – 0xbadf00d

4

con un modello variadic, il numero di argomenti è noto durante la compilazione (e accessibili tramite sizeof...). Con un std::initializer_list, il numero di argomenti è noto solo al runtime. Quindi parte della decisione dipende da quando hai bisogno o vuoi sapere quanti argomenti hai.

+0

il contenitore ha dimensioni di compilazione, poiché deve essere inizializzato nello stato completo (non è possibile push_back, ecc.). se intendevi il problema del suo metodo ':: size()' non contrassegnato come 'constexpr', questo era un difetto in C++ 11 e risolto in C++ 14. in modo che tu possa template sulla dimensione di un nome 'initializer_list', ecc. in molti modi, la dimensione è disponibile in fase di compilazione. ma certo, forse non in molti modi come l'opzione variadica! –

+2

Un costruttore _parameter_ che è un 'initializer_list' non ha una dimensione nota durante la compilazione, perché il costruttore può essere chiamato da più siti con inizializzatori di dimensioni diverse su ciascun sito. – KnowItAllWannabe

+0

Sì, ottimo punto, non stavo pensando completamente prima :) –