2015-10-09 11 views
12

Ho una classe astratta per i valori comparabili + hashable:C++ meta-programmazione: un parametro di template che * deve * ereditare una classe astratta

class Key 
{ 
public: 
    virtual bool operator ==(const Key&) const = 0; 
    virtual bool operator !=(const Key&) const = 0; 
    virtual u32 hashcode() const = 0; 
}; 

e un po 'di classe C concreta che eredita questo.

class C : public Key 
{ 
private: 
    u32 a, b; 
public: 
    static const C& null; // a prototype for representing a "no value" C 
    // Some reasonable implementation; it's just a pair 
    // ... 
}; 

e desidero implementare una classe HashSet template:

template<class T inherits Key, const T& proto> class HashSet 
{ 
    //... 
}; 

T è il tipo di valori memorizzati in questi gruppi. proto dovrebbe essere un'istanza di T che viene utilizzata come valore "null" di tipo T ai fini dell'inclusione dell'insieme. Ho ragionevolmente esperienza con C++ ma non particolarmente con TMP e, sebbene sembri qualcosa che dovrebbe essere imbarazzantemente semplice da estrarre, non riesco a capire come qualcosa del mio pseudo-codice "classe T erediti Key" sia effettivamente fatto in C++. Voglio essere in grado di creare un hash-serie di istanze di C come:

HashSet<C, C::null> myset; 

Qualcuno può per favore mi dica cosa il modo corretto e idiomatica per gestire questa situazione in C++ sarebbe? Grazie!

+3

['std :: enable_if'] (http://en.cppreference.com/w/cpp/types/enable_if) e [' std :: is_base_of'] (http://en.cppreference.com/ w/cpp/tipi/is_base_of). – Biffen

+3

Non capirò mai perché un 'std :: enable_if' nascosto in un elenco di parametri template di un modello primario sia preferito a un' static_assert' con un messaggio user-friendly –

+2

@PiotrSkotnicki Direi che è un compromesso tra un migliore messaggio di errore e avere i vincoli di tipo documentati direttamente nella dichiarazione piuttosto che nascosti nella definizione. – TartanLlama

risposta

11

È possibile utilizzare std::enable_if_t e std::is_base_of per questo:

template<class T, const T& proto, 
     std::enable_if_t<std::is_base_of<Key,T>::value>* = nullptr> 
class HashSet 
{ 
    //... 
}; 

Ora HashSet istanze sono valide solo se T eredita da Key.

std::enable_if_t è una funzionalità di C++ 14. Puoi usare typename std::enable_if<...>::type se sei bloccato con C++ 11.

Live Demo


Un'altra opzione sarebbe quella di utilizzare static_assert:

template<class T, const T& proto> 
class HashSet 
{ 
    static_assert(std::is_base_of<Key, T>::value, "T must inherit from Key"); 
}; 

Questo è forse un po 'più chiaro e ti dà un messaggio di errore più amichevole, ma il tipo di vincolo non è più dato nella dichiarazione di classe.


Con Concepts avremo chiarezza, i messaggi di errore migliori e mantenere i nostri vincoli nella dichiarazione:

template <class Base, class Derived>                                                   
concept bool IsBaseOf = std::is_base_of<Base, Derived>::value; 

template<class T, const T& proto> 
requires IsBaseOf<Key,T> 
class HashSet 
{}; 
+0

Wow, questo è lordo "miglioramento" in C++ 14 ... Questo non è un commento alla tua risposta, naturalmente. Apprezzo la soluzione. Grazie! – Thomas

+0

Sì, è piuttosto orribile. Come Piotr ha sottolineato nei commenti, è possibile utilizzare un 'static_assert' nella definizione della classe per ottenere un po 'di chiarezza e un messaggio di errore più bello a scapito di avere i vincoli di tipo documentati nella dichiarazione. – TartanLlama

+4

@Thomas: questo è il modo C++. Una trama di una sinistra cabala per trasformare la lingua in un'atrocità per mantenere basso il suo tasso di adozione, e il salario - alto;) – dtech

2

Qualcuno può dirmi quello che il modo corretto e idiomatica per gestire questa situazione in C++ sarebbe?

Questo sarebbe semplicemente non gestirlo. Se l'utente passa in un tipo che deriva da Key, l'istanziazione del modello funzionerà anche se non lo si aggiunge come requisito esplicito in un'annotazione di codice. Se l'utente inoltra un argomento modello non valido, le cose dovrebbero rompersi.

La prossima versione di C++ probabilmente hanno il supporto per l'inclusione chiaramente tali annotazioni, ma nella versione attuale di C++, mentre ci sono alcuni trucchi che è possibile utilizzare, salvo in circostanze limitate modo idiomatica è non solo di perdere tempo con esso .

+1

Direi che trucchi come 'std :: enable_if' sono diventati un modo di fatto di esprimere vincoli di tipo sia per l'utente che per il compilatore, ma credo che sia discutibile. – TartanLlama

+0

@TartanLlama Sì, sono d'accordo sul fatto che, quando i vincoli di tipo sono espressi, 'std :: enable_if' è il modo idiomatico per farlo, ma poiché si presenta come una specie di hack, esprimere dei vincoli di tipo è qualcosa che semplicemente non è Generalmente fatto ancora, almeno non da quello che ho visto. :) – hvd

+0

Grazie hvd. In realtà non mi ero neanche reso conto che non si sarebbe lamentato se non avessi istanziato un modello con una T che non supportava i metodi che usavo. Immagino che non sia né abbastanza intelligente da controllare questo, né abbastanza intelligente da permettermi di farlo elegantemente, quindi ... forse si uniforma. Giocherò con queste soluzioni e controllerò. Molto apprezzato! – Thomas