Modelli C ++ che accettano solo determinati tipi

In Java è ansible definire una class generica che accetta solo tipi che estendono la class di propria scelta, ad esempio:

public class ObservableList { ... } 

Questo viene fatto usando la parola chiave “estende”.

Esiste un semplice equivalente a questa parola chiave in C ++?

Suggerisco di utilizzare la funzione di asserzione statica di Boost in concerto con is_base_of dalla libreria Boost Type Traits:

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

In altri casi più semplici, puoi semplicemente inoltrare un modello globale, ma definirlo solo (esplicitamente o parzialmente) per i tipi validi:

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

[MODIFICA MINORE 6/12/2013: l’utilizzo di un modello dichiarato ma non definito comporterà linker , non compilatore, messaggi di errore.]

Questo in genere è ingiustificato in C ++, come hanno notato altre risposte qui. In C ++ tendiamo a definire tipi generici basati su altri vincoli diversi da “eredita da questa class”. Se volessi davvero farlo, è abbastanza semplice farlo in C ++ 11 e :

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

Questo però rompe molti dei concetti che le persone si aspettano in C ++. È meglio usare trucchi come definire i propri tratti. Ad esempio, forse observable_list vuole accettare qualsiasi tipo di contenitore che abbia il const_iterator e una funzione membro iniziale e end che restituisce const_iterator . Se si limita questo alle classi che ereditano list allora un utente che ha il proprio tipo che non eredita list ma fornisce queste funzioni membro e typedefs non sarebbe in grado di utilizzare la propria observable_list .

Ci sono due soluzioni a questo problema, una di queste è non limitare nulla e fare affidamento sulla digitazione anatra. Un big ostacolo a questa soluzione è che comporta una quantità massiccia di errori che possono essere difficili da eliminare per gli utenti. Un’altra soluzione è definire i tratti per vincolare il tipo fornito per soddisfare i requisiti dell’interfaccia. La grande truffa per questa soluzione è che comporta una scrittura extra che può essere considerata fastidiosa. Tuttavia, il lato positivo è che sarai in grado di scrivere i tuoi messaggi di errore a la static_assert .

Per completezza, la soluzione all’esempio sopra è data:

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

Ci sono molti concetti mostrati nell’esempio sopra che mostrano le caratteristiche del C ++ 11. Alcuni termini di ricerca per i curiosi sono modelli variadici, SFINAE, espressione SFINAE e caratteri tipografici.

La soluzione semplice, che nessuno ha ancora menzionato, è semplicemente ignorare il problema. Se provo a utilizzare un int come un tipo di modello in un modello di funzione che si aspetta una class contenitore come vettore o lista, allora otterrò un errore di compilazione. Greggio e semplice, ma risolve il problema. Il compilatore proverà ad usare il tipo specificato, e se fallisce, genera un errore di compilazione.

L’unico problema è che i messaggi di errore che ottieni saranno difficili da leggere. È tuttavia un modo molto comune per farlo. La libreria standard è piena di modelli di funzioni o di class che si aspettano determinati comportamenti dal tipo di modello e non fanno nulla per verificare che i tipi utilizzati siano validi.

Se vuoi messaggi di errore più accurati (o se vuoi rilevare casi che non genererebbero un errore del compilatore, ma comunque non hanno senso) puoi, a seconda di quanto complesso vuoi farlo, utilizzare l’asser statico di Boost o la libreria concept_check di Boost.

Con un compilatore aggiornato hai un built_in static_assert , che potrebbe essere usato al suo posto.

Possiamo usare std::is_base_of e std::enable_if :
( static_assert può essere rimosso, le classi precedenti possono essere implementate su misura o utilizzate da boost se non possiamo fare riferimento a type_traits )

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

Per quanto ne so, questo non è attualmente ansible in C ++. Tuttavia, ci sono piani per aggiungere una funzione chiamata “concetti” nel nuovo standard C ++ 0x che fornisce la funzionalità che stai cercando. Questo articolo di Wikipedia sui concetti C ++ lo spiegherà in modo più dettagliato.

So che questo non risolve il tuo problema immediato, ma ci sono alcuni compilatori C ++ che hanno già iniziato ad aggiungere funzionalità dal nuovo standard, quindi potrebbe essere ansible trovare un compilatore che abbia già implementato la funzione dei concetti.

Un equivalente che accetta solo i tipi T derivati ​​dall’elenco dei tipi

 template::value>::type* = nullptr> class ObservableList { // ... }; 

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

I generici di Java non sono uguali ai modelli ; usano la cancellazione di tipo , che è una tecnica dynamic , piuttosto che il polimorfismo del tempo di compilazione , che è una tecnica statica . Dovrebbe essere ovvio perché queste due tattiche molto diverse non si gelificano bene.

Piuttosto che tentare di utilizzare un costrutto di tempo di compilazione per simulare un tempo di esecuzione, diamo un’occhiata a ciò che effettivamente extends : secondo Stack Overflow e Wikipedia , extends è usato per indicare la sottoclass.

C ++ supporta anche la sottoclass.

Mostra anche una class contenitore, che utilizza la cancellazione del tipo sotto forma di un generico e si estende per eseguire un controllo del tipo. In C ++, devi fare da solo il macchinario di cancellazione del tipo, che è semplice: fai un puntatore alla superclass.

Raggruppiamolo in un typedef, per renderlo più facile da usare, piuttosto che creare un’intera class, et voilà:

typedef std::list subclasss_of_superclass_only_list;

Per esempio:

 class Shape { }; class Triangle : public Shape { }; typedef std::list 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 List sia un’interfaccia, che rappresenta una sorta di collezione. Un’interfaccia in C ++ sarebbe semplicemente una class astratta, ovvero una class che non implementa nient’altro che metodi virtuali puri. Usando questo metodo, potresti facilmente implementare il tuo esempio java in C ++, senza alcuna specializzazione di modelli o di concetti. Avrebbe anche prestazioni lente come i generici in stile Java a causa delle ricerche nella tabella virtuale, ma spesso può essere una perdita accettabile.

Sommario esecutivo: non farlo.

La risposta di j_random_hacker ti dice come farlo. Tuttavia, vorrei anche sottolineare che non dovresti farlo. L’intero punto dei modelli è che possono accettare qualsiasi tipo compatibile e che i vincoli del tipo di stile Java lo infrangono.

I vincoli di tipo Java sono un bug e non una funzionalità. Sono lì perché Java digita la cancellazione sui generici, quindi Java non riesce a capire come chiamare i metodi basati sul solo valore dei parametri di tipo.

D’altra parte il C ++ non ha restrizioni del genere. I tipi di parametri del modello possono essere di qualsiasi tipo compatibili con le operazioni con cui sono utilizzati. Non ci deve essere una class base comune. Questo è simile a “Duck Typing” di Python, ma fatto in fase di compilazione.

Un semplice esempio che mostra la potenza dei modelli:

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

Questa funzione sum può sumre un vettore di qualsiasi tipo che supporta le operazioni corrette. Funziona con entrambi i primitivi come int / long / float / double e tipi numerici definiti dall’utente che sovraccaricano l’operatore + =. Diamine, puoi anche usare questa funzione per unire le stringhe, dal momento che supportano + =.

Non è necessario il boxing / unboxing delle primitive.

Si noti che costruisce anche nuove istanze di T usando T (). Questo è banale in C ++ usando interfacce implicite, ma non è realmente ansible in Java con vincoli di tipo.

Mentre i modelli C ++ non hanno vincoli di tipo espliciti, sono ancora sicuri, e non verranno compilati con codice che non supporta le operazioni corrette.

Ciò non è ansible in C ++, ma è ansible verificare i parametri del modello in fase di compilazione tramite Concept Checking, ad esempio utilizzando BCCL di Boost .

 class Base { struct FooSecurity{}; }; template class Foo { typename Type::FooSecurity If_You_Are_Reading_This_You_Tried_To_Create_An_Instance_Of_Foo_For_An_Invalid_Type; }; 

Assicurati che le classi derivate ereditino la struttura di FooSecurity e il compilatore si arrabbierà in tutti i posti giusti.

Esiste un semplice equivalente a questa parola chiave in C ++?

No.

A seconda di ciò che stai cercando di ottenere, potrebbero esserci sostituti adeguati (o anche migliori).

Ho esaminato alcuni codici STL (su Linux, penso che sia quello derivante dall’implementazione di SGI). Ha “asserzioni di concetto”; per esempio, se hai bisogno di un tipo che comprenda *x e ++x , l’asserzione di concetto conterrebbe quel codice in una funzione non-nulla (o qualcosa di simile). Richiede un po ‘di overhead, quindi potrebbe essere intelligente metterlo in una macro la cui definizione dipende da #ifdef debug .

Se la relazione sottoclass è davvero ciò che vuoi sapere, puoi affermare nel costruttore che T instanceof list (eccetto che è “scritto” in modo diverso in C ++). In questo modo, puoi testare la tua uscita dal compilatore senza poterlo controllare.

Non ci sono parole chiave per tali controlli di tipo, ma puoi inserire del codice che almeno non riuscirà in modo ordinato:

(1) Se si desidera che un modello di funzione accetti solo i parametri di una determinata class base X, assegnarlo a un riferimento X nella propria funzione. (2) Se si desidera accettare funzioni ma non primitive o viceversa, o si desidera filtrare le classi in altri modi, chiamare una funzione helper template (vuota) all’interno della propria funzione definita solo per le classi che si desidera accettare.

È ansible utilizzare (1) e (2) anche nelle funzioni membro di una class per forzare questi controlli di tipo sull’intera class.

Probabilmente puoi metterlo in qualche macro intelligente per alleviare il tuo dolore. 🙂

Bene, puoi creare il tuo template leggendo qualcosa come questo:

 template class ObservableList { std::list contained_data; }; 

Ciò tuttavia renderà implicita la restrizione, in più non puoi fornire nulla che assomigli ad una lista. Esistono altri modi per limitare i tipi di contenitori utilizzati, ad esempio facendo uso di specifici tipi di iteratore che non esistono in tutti i contenitori, ma anche questo è più implicito di una restrizione esplicita.

Per quanto ne so, un costrutto che rispecchierebbe la dichiarazione dell’istruzione Java in tutta la sua estensione non esiste nello standard attuale.

Ci sono modi per limitare i tipi che puoi usare all’interno di un modello che scrivi usando typedef specifici all’interno del tuo modello. Ciò assicurerà che la compilazione della specializzazione del modello per un tipo che non include quel particolare typedef avrà esito negativo, quindi è ansible supportare / supportare in modo selettivo determinati tipi.

In C ++ 11, l’introduzione di concetti dovrebbe renderlo più facile ma non penso che farà esattamente ciò che vorresti.