2010-09-13 13 views
31

Sto creando una funzione (possibilmente funzione membro, non che importi ... forse lo fa?) Che deve accettare un numero sconosciuto di argomenti, ma voglio che siano tutti dello stesso tipo. So che potrei passare in un array o in un vettore, ma voglio essere in grado di accettare l'elenco di argomenti direttamente senza struttura aggiuntiva o parentesi aggiuntive. Non sembra che le funzioni variadiche siano di per sé dattiloscritte, e non ero sicuro di come andare su queste funzioni di template w/variadic. Ecco in sostanza quello che sto puntando (più che probabile codice non corretto, e totalmente non allo scopo di ottenere liste di draghi, lol):Specificare un tipo per tutti gli argomenti passati alla funzione variadic o alla funzione modello variad/w usando la matrice, il vettore, le strutture, ecc.?

//typedef for dragon_list_t up here somewhere. 

enum Maiden { 
    Eunice 
    , Beatrice 
    , Una_Brow 
    , Helga 
    , Aida 
}; 

dragon_list_t make_dragon_list(Maiden...) { 
    //here be dragons 
} 

O

template<Maiden... Maidens> dragon_list_t make_dragon_list(Maidens...) { 
    //here be dragons 
} 

USO

dragon_list_t dragons_to_slay 
    = make_dragon_list(Maiden.Eunice, Maiden.Helga, Maiden.Aida) 
; 

Ho provato già alcune cose simili a quelle precedenti, senza dadi. Suggerimenti? Deviazioni evidenti che potrei aver fatto? So che non può essere un affare enorme per fare questo, invece:

dragon_list_t make_dragon_list(std::array<Maiden> maidens) { 
    //here be dragons. 
} 
dragon_list_t dragons_to_slay 
    = make_dragon_list({Maiden.Eunice, Maiden.Helga, Maiden.Aida}) 
; 

ma mi piacerebbe molto piuttosto essere in grado di farlo nel primo modo, se possibile.

risposta

21

Puoi semplicemente accettare gli argomenti dal modello variadic e lasciare che typechecking verifichi la validità più tardi quando vengono convertiti.

È possibile controllare la convertibilità a livello di interfaccia funzioni, però, di fare uso di risoluzione di sovraccarico per respingere gli argomenti a titolo definitivo sbagliate, ad esempio, utilizzando SFINAE

template<typename R, typename...> struct fst { typedef R type; }; 

template<typename ...Args> 
typename fst<void, 
    typename enable_if< 
    is_convertible<Args, ToType>::value 
    >::type... 
>::type 
f(Args...); 

Per il vostro caso d'uso se si conoscono i passi per va da un std::array<> al vostro dragon_list_t allora si hanno già risolto se secondo la prima opzione di cui sopra ("convertire-dopo"):

template<typename ...Items> 
dragon_list_t make_dragon_list(Items... maidens) { 
    std::array<Maiden, sizeof...(Items)> arr = {{ maidens ... }}; 
    // here be dragons 
} 

Se si combina questo con quanto sopra is_convertible approccio si dispone di un modello scarto-precoce che fa anche la risoluzione di sovraccarico su argomenti e li respinge se non applicabile.

+1

+1 per dare OP esattamente quello che ha chiesto, anche se è per il meglio o il peggio – Potatoswatter

+0

Non ho giocato con i modelli variadic abbastanza per sapere se la deccapottabile precoce è richiesta, ma +1 la conversione std :: array, non ci ho pensato. –

+1

Hmm, penso che il modello early-reject non sia valido per il caso generale. Se Dragon era una classe base, allora il tuo primo argomento (o tutti i tuoi argomenti), potrebbe essere una classe derivata, e quindi non convertibile l'uno con l'altro. Quando dragon è un enum, potrebbe essere la stessa cosa se la tua prima classe è convertibile in un tipo intero ma non un tipo intero stesso. Penso che fare affidamento sul sovraccarico sia più estendibile. –

10

Dato che hai incluso il tag C++ 0x, la risposta ovvia sarebbe cercare initializer lists. Un elenco di inizializzatori consente di specificare un numero di argomenti per un ctor che verrà automaticamente convertito in una singola struttura di dati per l'elaborazione da parte del ctor.

Il loro uso principale (esclusivo?) È esattamente per il tipo di situazione che hai menzionato, passando un numero di argomenti dello stesso tipo da utilizzare nella creazione di una sorta di elenco/array/altra raccolta di oggetti. Sarà sostenuto da (per un esempio) std::vector, così si potrebbe usare qualcosa come:

std::vector<dragon> dragons_to_slay{Eunice, Helga, Aida}; 

per creare un vettore di tre dragon oggetti. La maggior parte (tutte?) Delle altre collezioni includerà lo stesso, quindi se davvero insisti su un elenco di draghi dovresti essere in grado di ottenere anche quello abbastanza facilmente.

+0

Non ho nemmeno pensato di guardare le liste di init, grazie per il suggerimento. Ancora masticando tutte queste risposte: \ –

1

Dipende molto da ciò che stai cercando di implementare, esattamente.

Solitamente enum indica sottotipi di runtime di una particolare classe o unione discriminata (boost :: variant). Ma in questo caso, si desidera passare direttamente lo enum. Inoltre, si dispone di un insieme limitato di valori possibili e ogni chiamata di funzione costituisce un sottoinsieme. In realtà ciò che si rappresenta è un sottoinsieme, non diversi parametri.

Il modo migliore per rappresentare un sottoinsieme di un set finito è un set di bit. I set di grandi dimensioni dovrebbero usare std::bitset; piccoli set possono usare solo unsigned long.

enum Maiden_set { 
    Eunice = 1, 
    , Beatrice = 2 
    , Una_Brow = 4 
    , Helga = 8 
    , Aida = 16 
}; 

dragon_list_t make_dragon_list(Maiden_set) { 
    //here be dragons 
} 

make_dragon_list(Eunice + Beatrice + Helga); 

o, dal momento in cui sembra voler gestire la variazione in fase di compilazione,

template< int Maidens > // parameter is not a Maiden_set because enum+enum=int 
dragon_list_t make_dragon_list() { 
    //here be dragons 
} 

make_dragon_list< Eunice + Beatrice + Helga >(); // + promotes each enum to int 

Dovrebbe essere possibile generare le potenze di 2 utilizzando automaticamente un operator+ sovraccarico sul tipo enum. Ma non sono sicuro di essere sulla buona strada.

+0

Puoi inserire bitshift in fase di compilazione, ovvero, enum {first_value = 1, second_value = 1 << 1, third_value = 1 << 2}; ' – Puppy

+0

@DeadMG: scriverei in questo modo, ma sarete sorpresi di quanti lettori non abbiano mai visto prima quell'operatore. – Potatoswatter

+0

Ho, ma per curiosità, ci sarebbero dubbi sulla bit-endianness? So che probabilmente è oscuro. Non so con quale CPU sarebbe un problema, e non è che sto cercando di costruire qualcosa che sia al 100% multipiattaforma o altro. Solo curioso. –

1

In breve, probabilmente dovresti semplicemente creare un vettore. Non è un sovraccarico, specialmente se si utilizza qualcosa come l'elenco di inizializzazione di boost::list_of o C++ 0x. L'overhead sintattico è minimo ed è più flessibile (è possibile passare l'elenco con un numero di argomenti noti solo al runtime).

Se si voleva davvero, è possibile utilizzare parametri di modello variadic per fare questo:

// Using pass-by-value since I'm assuming it is primitive: 

template< typename T, typename... Args> 
void make_dragon_list_internal(dragon_list_t* dragon_list, T t, Args... args) 
{ 
    // add T to dragon_list. 
    make_dragon_list_internal(dragon_list, args...); 
} 

void make_dragon_list_internal(dragon_list_t* dragon_list) 
{ 
    // Finalize dragon_list. 
} 

template<typename... Args> 
dragon_list_t make_dragon_list(Args... args) 
{ 
    dragon_list_t dragon_list; 
    make_dragon_list_internal(&dragon_list, args...); 
    return dragon_list; 
} 

E 'typesafe, ed estensibile (si potrebbe fare questo prendere le cose diverse da draghi, se avete voglia).

+0

Err e per typesafe, intendo, come typesafe del tipo sottostante, che per un enum non di classe non è molto. –

1

Vorrei provare a mantenere le cose semplici, e la soluzione più semplice che riesco a pensare è semplicemente usare un semplice vecchio vettore. Utilizzando C++ 0x caratteristiche è possibile ottenere una sintassi che è simile a (anche se non esattamente) ciò che si vuole:

void foo(std::vector<int> const & v) { 
    std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ")); 
} 
int main() { 
    foo({ 1, 2, 3, 4, 5, 6 }); // note the extra {} 
} 
8

Se non si utilizza template sul parametro non nella confezione della Funzione Variadica risolverà per avere tutti gli argomenti dello stesso tipo.

Ecco un esempio di una funzione estesa max che accetta solo int s (o tipi convertibili in int).

int maximum(int n) // last argument must be an `int` 
{ 
    return n; 
} 

template<typename... Args> 
int maximum(int n, Args... args) // first argument must be an int 
{ 
    return std::max(n, maximum(args...)); 
} 

Spiegazione: Quando si estrae la confezione argomento (args...) il compilatore cerca il miglior sovraccarico. Se il pacchetto ha solo un parametro, l'unico candidato è maximum(int) quindi l'unico parametro deve essere e di tipo int (o convertibile in int). Se ci sono più elementi nel pacchetto, l'unico candidato è maximum(int, typename...) quindi il primo argomento deve essere di tipo int (o convertibile in int). È semplice dimostrare per induzione che tutti i tipi nel pacchetto devono essere di un tipo convertibile in int).

+0

Interessante ... non posso dire di aver capito perché funziona, qualche informazione/link su questo? –

+0

@pheadbaq, ho aggiunto una spiegazione nella mia risposta. – Motti

4

Recentemente ho avuto bisogno di vincolare un pacchetto di parametri per essere solo un tipo, o almeno convertibile per quel tipo. Ho finito per trovare un altro modo:

#include <type_traits> 
#include <string> 

template <template<typename> class Trait, typename Head, typename ...Tail> 
struct check_all { 
    enum { value = Trait<Head>::value && check_all<Trait, Tail...>::value }; 
}; 

template <template<typename> class Trait, typename Head> 
struct check_all<Trait, Head> { 
    enum { value = Trait<Head>::value }; 
}; 

template <typename ...Args> 
struct foo { 
    // Using C++11 template alias as compile time std::bind 
    template <typename T> 
    using Requirement = std::is_convertible<double, T>; 
    static_assert(check_all<Requirement, Args...>::value, "Must convert to double"); 
}; 

int main() { 
    foo<int, char, float, double>(); 
    foo<int, std::string>(); // Errors, no conversion 
} 

La cosa che mi è piaciuto di questa soluzione è che posso applicare check_all ad altri tratti anche.

0

Credo che il seguente codice è utile per il vostro caso:

template <class...> 
struct IsAllSame {}; 

template <class T, class B1> 
struct IsAllSame<T, B1> { 
    static constexpr const bool kValue = std::is_same<T, B1>::value; 
}; 

template <class T, class B1, class... Bn> 
struct IsAllSame<T, B1, Bn...> { 
    static constexpr const bool kValue = 
     IsAllSame<T, B1>::kValue ? IsAllSame<T, Bn...>::kValue : false; 
}; 

IsAllSame<int>::kValue == true 
IsAllSame<bool, int>::kValue == false 
IsAllSame<bool, int, int>::kValue == false 
1

Anche se la questione è aggiunto C++ 11, penso che una soluzione di C++ 17 + concetti sarebbe la pena di aggiungere visto che se ci ora è supportato in GCC e altri seguiranno presto.

prima definire un concetto semplice

class mytype{}; 

template<typename T> 
concept bool MyType = std::is_same<T, mytype>::value; 

poi semplicemente utilizzare i parametri del modello variadic

template<MyType ... Args> 
void func(Args &&... args){ 
    // do something here 
} 

molto più facile con l'avvento di concetti!

+0

I concetti che sono stati convertiti in C++ 2a (finora) non lo includono. [Vedi qui] (https://botondballo.wordpress.com/2017/08/02/trip-report-c-standards-meeting-in-toronto-july-2017/#concepts). – Noein

Problemi correlati