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?
risposta
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
}
Espansione sul bit dell'inoltro perfetto, 'std :: initializer_list
@LucDanton: buon punto. –
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
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
@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
@ipc Che cosa sta succedendo esattamente in exp_sequence {(data.push_back (std :: forward
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.
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! –
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
Sì, ottimo punto, non stavo pensando completamente prima :) –
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? –
@JoachimPileborg, assolutamente corretto, sebbene potessi scegliere tra 'int ...' e 'std :: initializer_list'. Dico scegli ciò che sembra più naturale da usare. –
chris