Template C++ che accettare solo determinati tipi di

In Java è possibile definire la classe generica che accettano solo i tipi che estende la classe della vostra scelta, ad esempio:

public class ObservableList<T extends List> {
  ...
}

Questo viene fatto utilizzando “estende” parola chiave.

C’è qualche semplice equivalente a questa parola in C++?

  • abbastanza vecchia questione già…mi sento quello che manca qui (anche dalle risposte) è che generics Java arent davvero un equivalente del template in C++. Ci sono delle somiglianze, ma imho si dovrebbe essere attenti con direttamente la traduzione di una soluzione java a C++ solo per rendersi conto che essi sono forse fatto per diversi tipi di problemi 😉
InformationsquelleAutor mgamer | 2009-05-17



13 Replies
  1. 98

    Io suggerisco di usare il Boost del statico affermare funzione in concerto con is_base_of dalla Spinta Tipo Tratti biblioteca:

    template<typename T>
    class ObservableList {
        BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
        ...
    };

    In alcuni altri, i casi più semplici, si può semplicemente inoltrare la dichiarazione di un modello globale, ma solo definire (in modo esplicito o parzialmente specializzati) per i tipi validi:

    template<typename T> class my_template;     //Declare, but don't define
    
    //int is a valid type
    template<> class my_template<int> {
        ...
    };
    
    //All pointer types are valid
    template<typename T> class my_template<T*> {
        ...
    };
    
    //All other types are invalid, and will cause linker error messages.

    [MODIFICA minore 6/12/2013: Utilizza dichiarato-ma-non-modello definito comporterà linker, non compilatore, i messaggi di errore.]

    • Statico afferma sono bello così. 🙂
    • Non voglio Boost, io sono su piattaforma embedded/telefono. Sarebbe template<> my_template<myBaseType> { eseguire parziale di specializzazione per tutti i tipi derivati da myBaseType troppo?
    • Ho paura che la specializzazione non potrà corrispondere myBaseType esattamente. Prima di congedare Boost, si dovrebbe sapere che la maggior parte di esso è solo intestazioni modello di codice, quindi non c’è nessuna memoria o costo di tempo in fase di runtime per le cose che non uso. Anche le cose particolari si sarebbe utilizzando qui (BOOST_STATIC_ASSERT() e is_base_of<>) può essere implementato utilizzando solo dichiarazioni (cioè non il definizioni di funzioni o variabili) in modo da non prendere qualsiasi spazio o di tempo.
    • C++11 è arrivato. Ora siamo in grado di utilizzare static_assert(std::is_base_of<List, T>::value, "T must extend list").
    • Mi piace questa risposta, perché la soluzione viene illustrato un modo per tirarlo fuori se non si dispone di boost o non si desidera utilizzare la spinta. Io lavoro in embedded spazio con l’utilizzo di speciali GCC strumento di catena da un fornitore senza il supporto per il boost, ma supporta i modelli. thumbs up!
    • Ho cercato di imitare pazzo generics Java uso: class UnitType<T extends UnitType<T>>class Unit<T extends UnitType<T>> e io non riuscivo a farlo funzionare. Fortunatamente, ho capito che una tale follia non sarebbe necessario, con i modelli C++:)
    • Grazie per la nota circa il doppio parentesi essere necessario. Ho avuto errori del compilatore cercando di utilizzare BOOST_STATIC_ASSERT in combinazione con is_base_of fino a quando ho trovato la tua risposta (ero con la sola parentesi).
    • Sei il benvenuto @jfritz42 🙂
    • BTW, la ragione per la doppia parentesi è necessario è che BOOST_STATIC_ASSERT è una macro e l’extra parentesi di evitare il preprocessore dall’interpretazione del comma entro il is_base_of gli argomenti di una funzione come un 2 ° macro argomento.
    • Come utilizzare il modello in secondo esempio? Il codice completo per favore.
    • Non ho ben capito cosa manca. Si potrebbe provare a dichiarare una variabile my_template<int> x; o my_template<float**> y; e verificare che il compilatore permette a questi, e quindi dichiarare una variabile my_template<char> z; e verificare che non è così.

  2. 98

    Questo in genere è ingiustificato in C++, come altre risposte qui hanno notato. In C++ si tende a definire tipi generici basati su altri vincoli oltre a “eredita dalla classe”. Se davvero si voleva fare, è abbastanza facile da fare in C++11 e <type_traits>:

    #include <type_traits>
    
    template<typename T>
    class observable_list {
        static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
        //code here..
    };

    Questo rompe un sacco di concetti che la gente si aspetta in C++ però. È meglio usare trucchi come definire i vostri propri tratti. Per esempio, forse observable_list vuole accettare qualsiasi tipo di contenitore che ha la typedef const_iterator e un begin e end funzione membro che restituisce const_iterator. Se si limita a questo, per le classi che ereditano da list quindi un utente che ha il proprio tipo che non eredita da list ma fornisce queste funzioni membro e typedef non sarebbe in grado di utilizzare il observable_list.

    Ci sono due soluzioni a questo problema, uno di loro è quello di non vincolare nulla e si basano su duck typing. Un grande con questa soluzione è che si tratta di un enorme quantità di errori che possono essere difficile per gli utenti di grok. Un’altra soluzione è quella di definire i tratti di vincolare il tipo forniti per soddisfare i requisiti di interfaccia. Grande con questa soluzione è quella che coinvolge extra di scrittura che può essere visto come fastidioso. Tuttavia, il lato positivo è che si sarà in grado di scrivere i vostri messaggi di errore a la static_assert.

    Per completezza, la soluzione per l’esempio di cui sopra è data da:

    #include <type_traits>
    
    template<typename...>
    struct void_ {
        using type = void;
    };
    
    template<typename... Args>
    using Void = typename void_<Args...>::type;
    
    template<typename T, typename = void>
    struct has_const_iterator : std::false_type {};
    
    template<typename T>
    struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};
    
    struct has_begin_end_impl {
        template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
                             typename End   = decltype(std::declval<const T&>().end())>
        static std::true_type test(int);
        template<typename...>
        static std::false_type test(...);
    };
    
    template<typename T>
    struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};
    
    template<typename T>
    class observable_list {
        static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
        static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
        //code here...
    };

    Ci sono un sacco di concetti mostrato nell’esempio di cui sopra che la vetrina di C++11 caratteristiche. Alcuni termini di ricerca per i curiosi sono modelli variadic, SFINAE, espressione SFINAE, e il tipo di caratteristiche.

    • Non ho mai capito che C++ modelli di utilizzare il duck typing fino ad oggi. Tipo di bizzarri!
    • Dato l’ampio vincoli della politica C++ introdotto per C, non so perché, template<class T:list> è un concetto di offendere. Grazie per il suggerimento.
  3. 56

    La soluzione più semplice, che nessuno ha ancora citato, è quello di ignorare il problema. Se cerco di usare un int come un tipo di modello in un modello di funzione che si aspetta da una classe contenitore come un vettore o di una lista, poi ricevo un errore di compilazione. Grezzo e semplice, ma non risolve il problema. Il compilatore cercherà di utilizzare il tipo specificato, e se non funziona, si genera un errore di compilazione.

    L’unico problema è che i messaggi di errore si ha intenzione di essere difficile da leggere. Tuttavia è un modo molto comune per fare questo. La libreria standard è piena di funzione o di una classe di modelli che si aspettano certo comportamento dal tipo di modello, e non fare nulla per controllare che i tipi utilizzati sono validi.

    Se vuoi più bello messaggi di errore (o se si desidera catturare i casi che non producono un errore del compilatore, ma ancora non hanno senso) è possibile, a seconda di come si vuole farlo, utilizzare Spinta statica far valere o la Spinta concept_check biblioteca.

    Con un up-to-data compilatore si dispone di un built_in static_assert, che può essere usato al posto.

    • Sì, ho sempre pensato che i modelli sono la cosa più vicina a duck typing in C++. Se si dispone di tutti gli elementi necessari per un modello, può essere utilizzato in un modello.
    • Mi dispiace, non riesco a fare testa o croce di che. Che tipo di T, e da dove è questo codice? Senza un po ‘ di contesto, non ho la possibilità di capire che snippet di codice. Ma quello che ho detto è vero. Se si tenta di chiamare toString() su un tipo che non ha un toString funzione membro, quindi si otterrà un errore di compilazione.
    • la prossima volta, forse, si dovrebbe essere un po ‘ meno trigger-happy downvoting persone quando il problema è nel codice
    • ok. +1. Questa è stata una grande risposta solo cercando di fare il meglio. Mi dispiace per il fraintendimento. Ho pensato che stavamo parlando con il tipo come parametro per le classi non per la funzione di modelli, che suppongo siano membri del precedente ma hanno bisogno di invocare il compilatore di bandiera.
  4. 13

    Per quanto ne so questo non è attualmente possibile in C++. Tuttavia, ci sono piani per aggiungere una funzionalità denominata “concetti” nel nuovo C++0x standard che forniscono la funzionalità che stai cercando. Questo Articolo di Wikipedia su C++ Concetti spiegherà più in dettaglio.

    So che questo non risolve il problema immediato, ma ci sono alcuni compilatori C++ che hanno già iniziato ad aggiungere funzionalità dal nuovo standard, quindi potrebbe essere possibile trovare un compilatore, che ha già messo in atto i concetti di funzionalità.

    • I concetti sono stati eliminati dalla norma, purtroppo.
    • I vincoli e i concetti che dovrebbero essere adottate per C++20.
  5. 11

    Possiamo utilizzare std::is_base_of e std::enable_if:

    (static_assert può essere rimosso, le classi sopra indicate possono essere personalizzati implementato o utilizzato da boost se noi non possiamo di riferimento type_traits)

    #include <type_traits>
    #include <list>
    
    class Base {};
    class Derived: public Base {};
    
    #if 0   //wrapper
    template <class T> class MyClass /* where T:Base */ {
    private:
        static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
        typename std::enable_if<std::is_base_of<Base, T>::value, T>::type inner;
    };
    #elif 0 //base class
    template <class T> class MyClass: /* where T:Base */
        protected std::enable_if<std::is_base_of<Base, T>::value, T>::type {
    private:
        static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
    };
    #elif 1 //list-of
    template <class T> class MyClass /* where T:list<Base> */ {
        static_assert(std::is_base_of<Base, typename T::value_type>::value , "T::value_type is not derived from Base");
        typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type base; 
        typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type::value_type value_type;
    
    };
    #endif
    
    int main() {
    #if 0   //wrapper or base-class
        MyClass<Derived> derived;
        MyClass<Base> base;
    // error:
        MyClass<int> wrong;
    #elif 1 //list-of
        MyClass<std::list<Derived>> derived;
        MyClass<std::list<Base>> base;
    // error:
        MyClass<std::list<int>> wrong;
    #endif
    // all of the static_asserts if not commented out
    // or "error: no type named ‘type’ in ‘struct std::enable_if<false, ...>’ pointing to:
    // 1. inner
    // 2. MyClass
    // 3. base + value_type
    }
  6. 10

    Un equivalente che accetta solo i tipi di T derivata dal tipo di Lista sembra

    template<typename T, 
             typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr>
    class ObservableList
    {
        //...
    };
  7. 7

    Penso che tutte le precedenti risposte hanno perso di vista la foresta per gli alberi.

    Generics Java non sono gli stessi modelli di; utilizzano tipo di cancellazione, che è un dinamica tecnica, piuttosto che di compilazione polimorfismo, che è statico tecnica. Dovrebbe essere ovvio il motivo per cui questi due molto diverse tattiche non gel bene.

    Piuttosto che cercare di utilizzare un tempo di compilazione di costruire per simulare un tempo di esecuzione di uno, vediamo cosa extends effettivamente: secondo Stack Overflow e Wikipedia, si estende viene utilizzato per indicare la creazione di sottoclassi.

    C++ supporta anche la creazione di sottoclassi.

    Si mostrano anche un contenitore di classe, che è tipo la cancellazione in forma di un generico, e si estende per eseguire un controllo di tipo. In C++, è necessario fare il tipo di cancellazione macchine di te stesso, che è semplice: rendere un puntatore alla superclasse.

    Lasciatevi avvolgere in un typedef, per rendere più facile da usare, piuttosto che fare una intera classe, et voilà:

    typedef std::list<superclass*> subclasses_of_superclass_only_list;

    Per esempio:

    class Shape { };
    class Triangle : public Shape { };
    
    typedef std::list<Shape*> only_shapes_list;
    only_shapes_list shapes;
    
    shapes.push_back(new Triangle()); //Works, triangle is kind of shape
    shapes.push_back(new int(30)); //Error, int's are not shapes

    Ora, sembra che la Lista è un’interfaccia, che rappresenta una sorta di raccolta. Un’interfaccia in C++ sarebbe soltanto una classe astratta è una classe che implementa nulla, ma per pura metodi virtuali. Utilizzando questo metodo, è possibile implementare facilmente il vostro java esempio in C++, senza alcun concetto o modello di specializzazione. Sarebbe anche eseguire lento come stile Java generics a causa di un tavolo virtuale look up, ma spesso può essere una perdita accettabile.

    • Io non sono un fan di risposte che usare frasi come “dovrebbe essere ovvio, o” tutti sanno”, e poi vai a spiegare che cosa è ovvia, o universalmente noto. Evidente è relativo al contesto, l’esperienza e il contesto di esperienza. Tali dichiarazioni sono intrinsecamente rude.
    • E ‘ da circa due anni è troppo tardi per essere criticato da questa risposta per il galateo, ma sono anche in disaccordo con voi, in questa specifica istanza; ho spiegato perché sono due tecniche non vanno insieme prima, affermando che era ovvio, non dopo. Ho fornito il contesto, e poi ha detto che la conclusione da quel contesto era ovvio. Che non è esattamente misura il vostro stampo.
  8. 6

    Sintesi: non che.

    j_random_hacker risposta ti dice come per fare questo. Tuttavia, vorrei anche sottolineare che si dovrebbe non fare questo. Il punto di modelli è che sono in grado di accettare qualsiasi tipo compatibile, e in stile Java vincoli di tipo break che.

    Java limitazioni di tipo un bug e non una funzione. Ci sono perché Java non il tipo di cancellazione farmaci generici, in modo che Java non riesco a capire come chiamare i metodi in base al valore dei parametri di tipo solo.

    C++ invece non ha restrizioni. Modello i tipi di parametro può essere di qualsiasi tipo compatibile con le operazioni in cui vengono utilizzati. Ci non deve essere una classe base comune. Questo è simile a Python “Duck Typing”, ma di fatto al momento della compilazione.

    Un semplice esempio che mostra la potenza di modelli:

    //Sum a vector of some type.
    //Example:
    //int total = sum({1,2,3,4,5});
    template <typename T>
    T sum(const vector<T>& vec) {
        T total = T();
        for (const T& x : vec) {
            total += x;
        }
        return total;
    }

    Questa funzione somma può somma di un vettore di qualsiasi tipo di supporto per il corretto funzionamento. Funziona sia con le primitive come int/long/float/double, e definito dall’utente tipi di dati numerici che sovraccaricare l’operatore+=. Heck, si può anche utilizzare questa funzione per unire le stringhe, visto che supportano +=.

    Non boxing/unboxing di primitive è necessario.

    Nota che si costruisce anche le nuove istanze di T con T(). Questo è banale in C++ utilizzando implicita interfacce, ma non è davvero possibile in Java con vincoli di tipo.

    Mentre i modelli C++ non hanno esplicitamente il tipo di vincoli, sono ancora di tipo sicuro, e non può essere compilato con il codice che non supporta il corretto funzionamento.

    • Se stai suggerendo di non specializzata modelli si può anche spiegare perché è in lingua?
    • Ho il tuo punto, ma se il tuo modello argomento deve essere derivato da un tipo specifico, quindi è meglio avere una facile interpretazione del messaggio da static_assert rispetto al normale errore del compilatore vomito.
    • Sì, il C++ è più espressivo qui, ma è generalmente una buona cosa (perché siamo in grado di esprimere di più con meno), a volte si vuole deliberatamente limitare il potere diamo noi stessi, per ottenere la certezza che abbiamo completamente capire un sistema.
    • tipo specializzato è utile quando si desidera essere in grado di approfittare di qualche cosa che può essere fatto solo per alcuni tipi. per esempio, è un booleano ~normalmente~ un byte ciascuno, anche se un byte può ~normalmente~ 8 bit/booleane; un modello di raccolta classe può (e, nel caso di std::map fa) specializzati per booleano quindi è possibile comprimere i dati più strettamente per risparmiare memoria.
    • Inoltre, per chiarire, questa risposta non è dire “non specializzati” modelli ” è questo: non utilizzare questa funzione per cercare di limitare i tipi possono essere utilizzati con un modello.
  9. 5

    Che non è possibile in C++, ma è possibile verificare i parametri del modello a tempo di compilazione attraverso il Concetto di Controllo, ad esempio utilizzando Aumentare la BCCL.

    • Beh, è possibile, ma il concetto di controllo è ancora una buona idea. 🙂
    • Io in realtà significava che non era possibile in “pianura” C++. 😉
  10. 5
    class Base
    {
        struct FooSecurity{};
    };
    
    template<class Type>
    class Foo
    {
        typename Type::FooSecurity If_You_Are_Reading_This_You_Tried_To_Create_An_Instance_Of_Foo_For_An_Invalid_Type;
    };

    Assicurarsi che le classi derivate ereditano il FooSecurity struttura e il compilatore si arrabbiano in tutti i posti giusti.

    • Questo cosa comporta?
    • è utilizzato nel modello di classe. Se la classe, passò nel modello argomento, non FooSecurity, tentando di utilizzare genera un errore. Certo è che se la classe è passato nel modello argomento non FooSecurity non è derivato da Base.
  11. 1

    C’è qualche semplice equivalente a questa parola in C++?

    N.

    A seconda di che cosa si sta cercando di realizzare, ci potrebbe essere adeguata (o anche meglio) sostituisce.

    Ho guardato attraverso alcuni STL codice (su linux, penso sia quello derivante da SGI attuazione. È il concetto di “affermazioni”; per esempio, se avete bisogno di un tipo che capisce *x e ++x, il concetto di asserzione contiene quel codice in un “non fare niente” funzione (o qualcosa di simile). Richiede un po ‘ di sovraccarico, quindi potrebbe essere intelligente per mettere in una macro la cui definizione dipende dalla #ifdef debug.

    Se la sottoclasse rapporto è davvero quello che vuoi sapere, si potrebbe affermare nel costruttore che T instanceof list (tranne che è “scritto” in modo diverso in C++). In questo modo, è possibile testare il vostro modo di compilatore di non essere in grado di controllare per voi.

  12. 1

    C’è nessuna parola chiave per tale tipo di controlli, ma si può mettere un codice che almeno non riuscire in modo ordinato:

    (1) Se si desidera un modello di funzione per accettare solo i parametri di base di classe X, assegnarlo ad un X di riferimento in funzione.
    (2) Se si desidera accettare di funzioni, ma non le primitive o viceversa, o che si desidera filtrare le classi in altri modi, chiamare un (vuoto) modello di funzione di supporto all’interno della vostra funzione che è definita solo per le classi che si desidera accettare.

    È possibile utilizzare (1) e (2) anche in funzioni membro di una classe di forza di questi tipo di controlli su tutta la classe.

    Probabilmente si può mettere in qualche smart Macro per alleviare il tuo dolore. 🙂

  13. -2

    Bene, si potrebbe creare il tuo modello di leggere qualcosa di simile a questo:

    template<typename T>
    class ObservableList {
      std::list<T> contained_data;
    };

    Questo caso la limitazione implicita, in più è possibile non solo la fornitura di qualcosa che assomiglia a un elenco. Ci sono altri modi per limitare i tipi di contenitori utilizzati, per esempio facendo uso di specifici iteratore tipi che non sono presenti in tutti i contenitori, ma questa è ancora più implicita che esplicita limitazione.

    Al meglio della mia conoscenza, un costrutto che avrebbe specchio l’istruzione Java istruzione a tutta la sua estensione non esiste in corrente standard.

    Ci sono modi per limitare i tipi che si possono utilizzare all’interno di un modello di scrittura attraverso l’uso di particolari tipi di all’interno del vostro modello. Questo farà sì che la compilazione del modello di specializzazione per un tipo che non comprende quella particolare typedef avrà esito negativo, così si può scegliere di supporto/non supporta alcuni tipi.

    In C++11, l’introduzione di concetti dovrebbero rendere questo più facile, ma non credo che si metterà a fare esattamente ciò che desideri sia.

Lascia un commento