c++ classe modello; funzione arbitraria tipo di contenitore, come definirlo?

Bene, semplice modello di domanda. Posso dire di definire il mio modello di classe di qualcosa di simile a questo:

template<typename T>
class foo {
public:
    foo(T const& first, T const& second) : first(first), second(second) {}

    template<typename C>
    void bar(C& container, T const& baz) {
        //...
    }
private:
    T first;
    T second;
}

La domanda è il mio bar… ho bisogno di essere in grado di utilizzare un contenitore standard di qualche tipo, che è il motivo per cui ho inserito il template/typename C parte, per definire che tipo di contenitore. Ma a quanto pare non è questo il modo giusto per farlo, visto che la mia classe di test poi si lamenta che:

errore: ‘bar’ non è stato dichiarato in questo ambito

Quindi come posso fare per realizzare il mio bar funzione in modo corretto? Che è, come funzione il mio template di classe, con un arbitrario tipo di contenitore… il resto della mia classe modello funziona bene (ha anche altre funzioni che non si tradurrà in un errore), è solo che una funzione che è problematico.

MODIFICA:
Ok, la funzione specifica (bar) è un eraseInRange funzione, che cancella tutti gli elementi in un intervallo specificato:

void eraseInRange(C& container, T const& firstElement, T const& secondElement) {...}

E un esempio di come poteva essere usato sarebbe:

eraseInRange(v, 7, 19);

dove v è un vettore, in questo caso.

EDIT 2:
Stupido me! Avrei dovuto dichiarare la funzione al di fuori della mia classe, non è… abbastanza frustrante errore di fare. Comunque, grazie a tutti per l’aiuto, anche se il problema era un po ‘diversa, le informazioni che mi ha aiutato costruire la funzione, dal momento che dopo aver trovato il mio problema, ho avuto un po’ di altre piacevoli errori. Quindi, grazie a voi!

  • Si prega di fornire un esempio di come si utilizza il pippo<T> classe e piedi<T>::bar<C> metodo. Il problema probabilmente è nel vostro usando codice.
  • Il codice da te postato compila bene. Di dove sei effettivamente il problema?
  • Classe di test non capita di ereditare da pippo? Vedere parashift.com/c++-faq-lite/modelli.html#faq-35.19
  • Spero che la mia modifica cancella un po’? La classe di test è stato fornito per l’esercizio, quindi non dovrei cambiare come il metodo viene chiamato, ma lamentano la eraseInRange mehtod non essere stato dichiarato…
  • Non è così. So che il messaggio di errore completo è lungo, ma che potrebbe aiutare molto. O meglio esatta del codice con un po ‘ di contesto (le classi che sono coinvolti).
  • Poiché non sappiamo che cosa il vostro test driver sta facendo, non siamo in grado di aiutare molto. Il lavoro in classe per voi, ad esempio, se si utilizza eraseInRange te stesso con un vettore?

InformationsquelleAutor Fault | 2011-10-11



3 Replies
  1. 30

    Tratti soluzione.

    Generalizzare non più del necessario, e non di meno.

    In alcuni casi, questa soluzione potrebbe non essere sufficiente in quanto si adattano a qualsiasi modello con firma (ad esempio shared_ptr), nel qual caso si potrebbe fare uso di type_traits, molto simile a duck typing (modelli di anatra digitato in generale).

    #include <type_traits>
    
    //Helper to determine whether there's a const_iterator for T.
    template<typename T>
    struct has_const_iterator
    {
    private:
        template<typename C> static char test(typename C::const_iterator*);
        template<typename C> static int  test(...);
    public:
        enum { value = sizeof(test<T>(0)) == sizeof(char) };
    };
    
    
    //bar() is defined for Containers that define const_iterator as well
    //as value_type.
    template <typename Container>
    typename std::enable_if<has_const_iterator<Container>::value,
                            void>::type
    bar(const Container &c, typename Container::value_type const & t)
    {
      //Note: no extra check needed for value_type, the check comes for
      //      free in the function signature already.
    }
    
    
    template <typename T>
    class DoesNotHaveConstIterator {};
    
    #include <vector>
    int main () {
        std::vector<float> c;
        bar (c, 1.2f);
    
        DoesNotHaveConstIterator<float> b;
        bar (b, 1.2f); //correctly fails to compile
    }

    Un buon modello di solito non non restringere artificialmente il tipo di tipi per cui sono validi (perché dovrebbe?). Ma immaginate che nell’esempio di cui sopra è necessario avere accesso a un oggetto const_iterator, quindi è possibile utilizzare SFINAE e type_traits a mettere quei vincoli sulla funzione.

    O semplicemente per come la libreria standard non

    Generalizzare non più del necessario, e non di meno.

    template <typename Iter>
    void bar (Iter it, Iter end) {
        for (; it!=end; ++it) { /*...*/ }
    }
    
    #include <vector>
    int main () {
        std::vector<float> c;
        bar (c.begin(), c.end());
    }

    Per ulteriori esempi, guardare in <algorithm>.

    Questo approccio punto di forza la sua semplicità e si basa sui concetti di ForwardIterator. Funziona anche per le matrici. Se volete segnalare errori di diritto nella firma, si può combinare con tratti.

    std contenitori con firma come std::vector (non raccomandato)

    La soluzione più semplice è approssimata da Kerrek SB già, anche se non è valido il C++. Il corretto variante va in questo modo:

    #include <memory> //for std::allocator
    template <template <typename, typename> class Container, 
              typename Value,
              typename Allocator=std::allocator<Value> >
    void bar(const Container<Value, Allocator> & c, const Value & t)
    {
      //
    }

    Tuttavia: questo funziona solo per i contenitori che hanno esattamente due tipo di modello di argomenti, fallirà miseramente per std::map (grazie Luc Danton).

    Qualsiasi tipo di secondaria argomenti di modello (non raccomandato)

    La versione corretta per ogni parametro secondario, il conteggio è il seguente:

    #include <memory> //for std::allocator<>
    
    template <template <typename, typename...> class Container, 
              typename Value,
              typename... AddParams >
    void bar(const Container<Value, AddParams...> & c, const Value & t)
    {
      //
    }
    
    template <typename T>
    class OneParameterVector {};
    
    #include <vector>
    int main () {
        OneParameterVector<float> b;
        bar (b, 1.2f);
        std::vector<float> c;
        bar (c, 1.2f);
    }

    Tuttavia: questo ancora non sarà possibile per non-modello di contenitori (grazie Luc Danton).

    • Che cosa questo ha a che fare con error: 'bar' was not declared in this scope? Mi dispiace, ma OP codice sembra corretto. Vedo un sacco di template magia qui, ma nessuno di questo è come si accetta in genere un contenitore in un modello di funzione.
    • Quello che è successo per la programmazione generica? template<typename Container> void foo(Container& c) { /* use typename Container::value_type as needed etc. */ ...
    • Solo una nota: garantire al momento della compilazione del Contenitore::value_type e T sono uguali (o che il Contenitore è un contenitore) è il lavoro di una statica asserzione. Confrontare la diagnostica di errore che si ottiene da innocente errori: ideone.com/gVVFU (come utile è quella!) e ideone.com/ILIGP (con il dove e il perché). Quello che vedete qui sopra è solo consigliabile se si sta andando a fornire un sovraccarico in cui questi tipi non sono la stessa cosa, o dove il primo argomento non è un contenitore.
    • Non è successo niente, e che è ancora la soluzione migliore (vedi la parte sul tratto il check-in la mia risposta). Mi ero tentato dalla prospettiva di avere una vera frase pubblicata che usa la parola “modello” quattro volte…
    • 0) Domanda era So how would I go about implementing my bar function the proper way?. 1) Definire “normale”. La libreria standard norma funziona su iteratore coppie (la mia quarta proposta). E. g. std::count‘s firma: count ( ForwardIterator first, ForwardIterator last, const T& value );.
    • Danton: Che provoca gli errori nel corpo della funzione, anziché sulla firma e si avrà un tempo più difficile che vieta la tua funzione non di contenitori. La mia supposizione è che gli utenti della biblioteca otterrà una migliore idea da “firma ” errori” che da “corpo di errori”. A parte questo, non capisco la tua domanda “che Cosa è successo per la programmazione generica”.
    • Buon punto static_assert, ma non vedo dove il mio codice non già affermare che, ad esempio, la firma di dire foo(Container<T>, T) o foo(Container, Container::value_type). In questo senso, non riescono a raggiungere la static_assert, perché non riesce a firma già.
    • Come di consueto in programmazione generica, si può avere personalizzato i messaggi dell’utente tramite ad esempio il concetto di controllo e/o static_assert. Utilizzando un binario di modello parametri del modello significa non essere compatibile con std::map. Quale delle due è più generico? (C++11 consente di variadic modello parametri del modello, ma che ancora limita contro i non-modello di contenitori.)
    • Danton: capisco quello che vuoi dire (è tutto il contrario di ipergeneralizzazione), vorrei aggiungere che, come una sola osservazione, non tratti e non di gamma versione 🙂 Ancora, la tua versione potrebbe essere troppo generico. E. g., se si dispone di due funzioni put(), uno per contenitori, uno per tipi scalari. Quindi come sarebbe la tua variante non resa non ambiguo le firme.
    • Il codice afferma, ma in un modo che produce un inutile messaggio di errore. Il che è particolarmente grave nel contesto della presente domanda: non solo di non risolvere il problema, si è suggerito di complicazioni che si sono tenuti a produrre anche più di quelli criptico errori apparentemente il codice corretto. – Come a suggerire di iteratore coppie, come il modo “normale”: se è necessario un contenitore (per cancellare), è praticamente fuori discussione, giusto? – Mi chiedo ancora come un rispettabile membro fornisce una risposta che si dà in modo molto discutibile e completamente off-topic consigli. 🙂
    • Quindi si deve anche mettere in discussione la sanità mentale di chi fa le domande per l’accettazione di un “completamente off-topic, consiglio” di w.r.t. c++ template class; function with arbitrary container type, how to define it? … “:)”. E anche rispondere a questa domanda: si Supponga di disporre di template<typename T> foo (Cont<T>, T), si vuole introdurre un ulteriore tipo di modello argomento, come template<typename T, typename U> foo (Cont<T>, U) solo per essere in grado di cadere in un corpo, rendendo quindi comunicare si trova (“posso prendere qualsiasi tipo!”, l’utente fornisce qualsiasi tipo, “Ma non posso, sciocco!”?
    • SFINAE permette di sovraccarico, ma a costo di leggibile messaggi di errore (si possono comunque fornire un catch-all che gli errori, ma in endthat è un sacco di lavoro). Comunque quei compromessi sono la solita roba di programmazione generica.
    • Il richiedente risolto il loro problema di se stessi, si rese conto che la domanda è senza risposta, e probabilmente accettato questa risposta per lo sforzo che mettete in esso. (Anche un principiante non ha l’esperienza per valutare queste cose.) – Sì, “ma non posso, sciocco!” comunica informazioni utili. “Non corrispondente funzione di chiamata”, non tanto per individuare la funzione (nel codice o manuale) per capirlo. – BTW, non c’è nulla nella domanda di suggerire che il richiedente è in cerca di limitare i tipi di input in questo modo!!
    • Mi è difficile scrivere la funzione non corretta firme per il bene dei migliori messaggi di errore, ed è nel modo di sovraccarico e di specializzazione (che se la vogliono davvero due funzioni f(Cont<T>, T) e f(Cont<T>,U)? Naturalmente, si potrebbe aggiungere un riferimento indiretto a risolvere, ma poi il messaggio di errore diventa ancora più remote, e la situazione è più di merda. Meglio: Imparare a leggere e comprendere la funzione delle firme e messaggi di errore, allora si può banalmente decifrare no matching function call XXX, candidates are YYY. […]
    • Tutto ciò che si desidera concetti, li voglio, troppo. Ma io non ho intenzione di befoul firme e diffondere menzogne su di accettazione e di comportamento. Inoltre, non è quello che chiamano self-documentare.
    • E sto ancora aspettando una risposta sul perché questa non è una risposta a funzione arbitraria tipo di contenitore, come definirlo?. La mia risposta non era specifica, ma generico. Problema? Non voglio? Va bene. Che opinione. Personalmente, sono felice che il ppl ha provato a darmi generico, riutilizzabili risposte. Altrimenti sarei ancora imparando cose del tipo “come posso uscita il 5 ° elemento di un std::vector<> quando il value_type è di tipo int e il mio ciclo for è il terzo ricorsione di una funzione che è stata chiamata tramite un puntatore a funzione che è stata const_casted da un riferimento che è stato …. che ……….

  2. 6

    Rendere il modello basato su modelli un modello parametro di modello:

    template <template <typename, typename...> class Container>
    void bar(const Container<T> & c, const T & t)
    {
      //
    }

    Se non si dispone di C++11, quindi non è possibile utilizzare modelli variadic, e si hanno il maggior numero di modello parametri del contenitore porta. Per esempio, per una sequenza contenitore potrebbe essere necessario due:

    template <template <typename, typename> class Container, typename Alloc>
    void bar(const Container<T, Alloc> & c, const T & t);

    O, se si vuole consentire i contatori, che sono istanze del modello:

    template <template <typename, typename> class Container, template <typename> class Alloc>
    void bar(const Container<T, Alloc<T> > & c, const T & t);

    Come ho suggerito nei commenti, io personalmente preferirei fare l’intero contenitore basato su modelli un tipo e utilizzare tratti per verificare se è valido. Qualcosa di simile a questo:

    template <typename Container>
    typename std::enable_if<std::is_same<typename Container::value_type, T>::value, void>::type
    bar(const Container & c, const T & t);

    Questo è più flessibile poiché il contenitore può ora essere qualsiasi cosa, che espone il value_type tipo di membro. Più sofisticate caratteristiche per il controllo per le funzioni membro e iteratori può essere concepito; per esempio, il bella stampante implementa alcuni di quelli.

    • Pseudo-+1: vorrei davvero avere più voti di oggi. 🙁
    • Qualsiasi motivazione perché che dovrebbe funzionare. (In generale, questo dovrebbe essere molto insolito cosa da fare…)
    • Che cosa vuoi dire? Si può abbinare a tutti i tipi di cose con i modelli. Non sto dicendo che questa è l’idea migliore (personalmente, avrei probabilmente preferito per un tratto di controllo di typename C::value_type), ma hey, è un’opzione.
    • Ma da dove si snoderà il pack?
    • Come funziona questo spiega perché il codice originale non riesce (ovviamente la domanda non fornisce abbastanza informazioni) e perché ciò possa avere successo? Non è ancora chiaro dalla domanda che T è supposto per essere C::value_type. Senza questa spiegazione, questo suona come woodoo programmazione di me.
    • Sono d’accordo con UncleBens. Non sottolineare i difetti nell’OP del codice (che mi pare di lavoro), e perché non a tratti la soluzione più elegante, compatto e leggibile?
    • I tratti sono preferibili perché consentono un più generale contenitori. Con il modello di parametro del modello, è necessario anticipare il corretto modello di firma. Con un tratto di controllo, si può avere tutto arbitrario contenitori, purché esporre value_type.
    • Guardate le frasi che terminano con un punto interrogativo e trovare la domanda. E magari si fermano a sputare fuoco e iniziare a rispondere?
    • Si potrebbe spiegare la grammatica: template <template <typename, typename...> class Container>? Non so come google una cosa del genere. Grazie.

  3. 0

    Ecco l’ultima e la versione estesa di questa risposta e un significativo miglioramento rispetto al risposta da Sabastian.

    L’idea è di definire tutti i tratti di contenitori STL. Purtroppo, questo diventa difficile, molto veloce e per fortuna un sacco di persone hanno lavorato sulla messa a punto di questo codice. Questi tratti sono quindi riutilizzabili solo copia e passato sotto il codice in un file chiamato type_utils.hpp (sentitevi liberi di modificare questi nomi):

    //put this in type_utils.hpp 
    #ifndef commn_utils_type_utils_hpp
    #define commn_utils_type_utils_hpp
    
    #include <type_traits>
    #include <valarray>
    
    namespace common_utils { namespace type_utils {
        //from: https://raw.githubusercontent.com/louisdx/cxx-prettyprint/master/prettyprint.hpp
        //also see https://gist.github.com/louisdx/1076849
        namespace detail
        {
            //SFINAE type trait to detect whether T::const_iterator exists.
    
            struct sfinae_base
            {
                using yes = char;
                using no  = yes[2];
            };
    
            template <typename T>
            struct has_const_iterator : private sfinae_base
            {
            private:
                template <typename C> static yes & test(typename C::const_iterator*);
                template <typename C> static no  & test(...);
            public:
                static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
                using type =  T;
    
                void dummy(); //for GCC to supress -Wctor-dtor-privacy
            };
    
            template <typename T>
            struct has_begin_end : private sfinae_base
            {
            private:
                template <typename C>
                static yes & f(typename std::enable_if<
                    std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::begin)),
                                 typename C::const_iterator(C::*)() const>::value>::type *);
    
                template <typename C> static no & f(...);
    
                template <typename C>
                static yes & g(typename std::enable_if<
                    std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::end)),
                                 typename C::const_iterator(C::*)() const>::value, void>::type*);
    
                template <typename C> static no & g(...);
    
            public:
                static bool const beg_value = sizeof(f<T>(nullptr)) == sizeof(yes);
                static bool const end_value = sizeof(g<T>(nullptr)) == sizeof(yes);
    
                void dummy(); //for GCC to supress -Wctor-dtor-privacy
            };
    
        }  //namespace detail
    
        //Basic is_container template; specialize to derive from std::true_type for all desired container types
    
        template <typename T>
        struct is_container : public std::integral_constant<bool,
                                                            detail::has_const_iterator<T>::value &&
                                                            detail::has_begin_end<T>::beg_value  &&
                                                            detail::has_begin_end<T>::end_value> { };
    
        template <typename T, std::size_t N>
        struct is_container<T[N]> : std::true_type { };
    
        template <std::size_t N>
        struct is_container<char[N]> : std::false_type { };
    
        template <typename T>
        struct is_container<std::valarray<T>> : std::true_type { };
    
        template <typename T1, typename T2>
        struct is_container<std::pair<T1, T2>> : std::true_type { };
    
        template <typename ...Args>
        struct is_container<std::tuple<Args...>> : std::true_type { };
    
    }}  //namespace
    #endif

    Ora è possibile utilizzare queste caratteristiche per assicurarsi che il nostro codice accetta solo i tipi di contenitori. Per esempio, è possibile implementare aggiungere la funzione che aggiunge un vettore ad un altro come questo:

    #include "type_utils.hpp"
    
    template<typename Container>
    static typename std::enable_if<type_utils::is_container<Container>::value, void>::type
    append(Container& to, const Container& from)
    {
        using std::begin;
        using std::end;
        to.insert(end(to), begin(from), end(from));
    }

    Avviso che sto usando begin() e end() dal namespace std, solo per essere sicuri abbiamo iteratore comportamento. Per ulteriori informazioni si veda la il mio post sul blog.

Lascia un commento