Come rilevare se esiste una variabile membro specifica in class?

Per creare la funzione template algoritmo ho bisogno di sapere se x o X (e y o Y) in class che è argomento template. Può essere utile quando si utilizza la mia funzione per la class CPoint MFC o GDI + PointF o altri. Tutti usano x differenti in loro. La mia soluzione potrebbe essere ridotta al seguente codice:

template struct TT {typedef int type;}; template bool Check_x(P p, typename TT::type b = 0) { return true; } template bool Check_x(P p, typename TT::type b = 0) { return false; } struct P1 {int x; }; struct P2 {float X; }; // it also could be struct P3 {unknown_type X; }; int main() { P1 p1 = {1}; P2 p2 = {1}; Check_x(p1); // must return true Check_x(p2); // must return false return 0; } 

Ma non si compila in Visual Studio, mentre si compila nel GNU C ++. Con Visual Studio potrei usare il seguente modello:

 template bool Check_x(P p, typename TT::type b = 0) { return true; } template bool Check_x(P p, typename TT::type b = 0) { return false; } 

Ma non viene compilato in GNU C ++. C’è una soluzione universale?

UPD: le strutture P1 e P2 qui sono solo per esempio. Ci potrebbero essere delle classi con membri sconosciuti.

PS Per favore, non pubblicare qui le soluzioni C ++ 11 perché sono ovvie e non rilevanti per la domanda.

Un altro modo è questo, che si basa anche su SFINAE per le espressioni . Se la ricerca del nome causa ambiguità, il compilatore rifiuterà il modello

 template struct HasX { struct Fallback { int x; }; // introduce member name "x" struct Derived : T, Fallback { }; template struct ChT; template static char (&f(ChT*))[1]; template static char (&f(...))[2]; static bool const value = sizeof(f(0)) == 2; }; struct A { int x; }; struct B { int X; }; int main() { std::cout << HasX::value << std::endl; // 1 std::cout << HasX::value << std::endl; // 0 } 

È basato su una brillante idea di qualcuno su usenet.

Nota: HasX verifica la presenza di dati o membri di funzioni chiamati x, con un tipo arbitrario. L'unico scopo di introdurre il nome del membro è di avere una ansible ambiguità per la ricerca del nome-membro: il tipo di membro non è importante.

Ecco una soluzione più semplice di Johannes Schaub – quella di litb . Richiede C ++ 11.

 #include  template  struct HasX : std::false_type { }; template  struct HasX  : std::true_type { }; 

Aggiornamento : un rapido esempio e la spiegazione su come funziona.

Per questi tipi:

 struct A { int x; }; struct B { int y; }; 

abbiamo HasX::value == true e HasX::value == false . Vediamo perché.

Prima di tutto ricordiamo che std::false_type e std::true_type hanno un valore static constexpr bool chiamato value che è impostato su false e true , rispettivamente. Quindi, i due modelli HasX sopra ereditano questo membro. (Il primo modello da std::false_type e il secondo da std::true_type .)

Iniziamo con semplicità e procediamo passo dopo passo fino a quando non arriveremo al codice sopra.

1) Punto di partenza:

 template  struct HasX : std::false_type { }; 

In questo caso, non c’è da sorprendersi: HasX deriva da std::false_type e quindi HasX::value == false e HasX::value == false .

2) Defaulting U :

 // Primary template template  struct HasX : std::false_type { }; 

Dato che U valore predefinito int , Has realtà significa HasX e, quindi, HasX::value == HasX::value == false .

3) Aggiunta di una specializzazione:

 // Primary template template  struct HasX : std::false_type { }; // Specialization for U = int template  struct HasX : std::true_type { }; 

In generale, grazie al modello principale, HasX deriva da std::false_type . Tuttavia, esiste una specializzazione per U = int che deriva da std::true_type . Pertanto, HasX::value == false ma HasX::value == true .

Grazie al valore predefinito per U , HasX::value == HasX::value == true .

4) decltype e un modo elegante per dire int :

Una piccola divagazione qui, ma, per favore, sopportami.

Fondamentalmente (questo non è del tutto corretto), decltype(expression) produce il tipo di espressione . Ad esempio, 0 ha tipo int così, decltype(0) significa int . Analogamente, 1.2 ha tipo double e quindi decltype(1.2) significa double .

Considera una funzione con questa dichiarazione:

 char func(foo, int); 

dove foo è un tipo di class. Se f è un object di tipo foo , decltype(func(f, 0)) significa char (il tipo restituito da func(f, 0) ).

Ora, l’espressione (1.2, 0) utilizza l’operatore virgola (incorporato) che valuta le due sottoespressioni in ordine (vale a dire prima 1.2 e poi 0 ), scarta il primo valore e produce il secondo. Quindi,

 int x = (1.2, 0); 

è equivalente a

 int x = 0; 

Mettendo questo insieme con decltype dà che decltype(1.2, 0) significa int . Non c’è niente di veramente speciale in 1.2 o in double qui. Ad esempio, true ha type bool e decltype(true, 0) significa int pure.

Che dire di un tipo di class? Per instace, cosa significa decltype(f, 0) significa? È naturale aspettarsi che questo significhi ancora int ma potrebbe non essere il caso. In effetti, potrebbe esserci un sovraccarico per l’operatore virgola simile alla funzione func precedente che prende un foo e un int e restituisce un char . In questo caso, decltype(foo, 0) è char .

Come possiamo evitare l’uso di un sovraccarico per l’operatore virgola? Beh, non c’è modo di sovraccaricare l’operatore virgola per un operando void e possiamo lanciare qualsiasi cosa per void . Pertanto, decltype((void) f, 0) significa int . Infatti, (void) f lancia f da foo a void che fondamentalmente non fa altro che dire che l’espressione deve essere considerata come avente tipo void . Quindi viene utilizzato l’operatore virgola incorporato e ((void) f, 0) restituisce 0 che ha tipo int . Quindi, decltype((void) f, 0) significa int .

Questo cast è davvero necessario? Bene, se non c’è sovraccarico per l’operatore virgola che foo e int questo non è necessario. Possiamo sempre ispezionare il codice sorgente per vedere se c’è un operatore del genere o meno. Tuttavia, se questo appare in un modello e f ha il tipo V che è un parametro di modello, allora non è più chiaro (o addirittura imansible sapere) se tale overload per l’operatore virgola esista o meno. Per essere generici, ci lanciamo comunque.

Bottom line: decltype((void) f, 0) è un modo elegante per dire int .

5) SFINAE:

Questa è una scienza completa 😉 OK, sto esagerando ma non è neanche molto semplice. Quindi terrò la spiegazione al minimo indispensabile.

SFINAE sta per Failover sostitutivo non è un errore. Significa che quando un parametro template viene sostituito da un tipo, potrebbe apparire un codice C ++ illegale ma, in alcune circostanze , invece di compilare la compilazione, il compilatore semplicemente ignora il codice incriminato come se non fosse lì. Vediamo come si applica al nostro caso:

 // Primary template template  struct HasX : std::false_type { }; // Specialization for U = int template  struct HasX  : std::true_type { }; 

Qui, ancora, decltype((void) T::x, 0) è un modo elegante per dire int ma con il beneficio di SFINAE.

Quando T è sostituito con un tipo, potrebbe apparire un costrutto non valido. Ad esempio, bool::x non è C ++ valido, quindi la sostituzione di T con bool in T::x produce un costrutto non valido. Secondo il principio SFINAE, il compilatore non rifiuta il codice, semplicemente ignora (parti di) esso. Più precisamente, come abbiamo visto HasX significa in realtà HasX . La specializzazione per U = int deve essere selezionata ma, durante l’istanziazione, il compilatore trova bool::x e ignora completamente la specializzazione del modello come se non esistesse.

A questo punto, il codice è essenzialmente identico al caso (2) sopra il quale esiste solo il modello principale. Quindi, HasX::value == false .

Lo stesso argomento utilizzato per bool vale per B poiché B::x è un costrutto non valido ( B non ha membro x ). Tuttavia, A::x è OK e il compilatore non vede alcun problema nell’istanziare la specializzazione per U = int (o, più precisamente, per U = decltype((void) A::x, 0) ). Quindi, HasX::value == true .

6) Unnaming U :

Bene, guardando ancora il codice in (5), vediamo che il nome U non è usato da nessuna parte ma nella sua dichiarazione ( typename U ). Possiamo quindi annullare il nome del secondo argomento del modello e otteniamo il codice mostrato nella parte superiore di questo post.

Sono stato reindirizzato qui da una domanda che è stata chiusa come duplicato di questo. So che è un thread vecchio, ma volevo solo suggerire un’implementazione alternativa (più semplice?) Che funzioni con C ++ 11. Supponiamo di voler verificare se una determinata class ha una variabile membro chiamata id :

 #include  template struct has_id : std::false_type { }; template struct has_id().id, void())> : std::true_type { }; 

Questo è tutto. Ed ecco come sarebbe usato ( esempio dal vivo ):

 #include  using namespace std; struct X { int id; }; struct Y { int foo; }; int main() { cout << boolalpha; cout << has_id::value << endl; cout << has_id::value << endl; } 

Le cose possono essere rese ancora più semplici con un paio di macro:

 #define DEFINE_MEMBER_CHECKER(member) \ template \ struct has_ ## member : false_type { }; \ template \ struct has_ ## member().member), void>::value, \ bool \ >::type \ > : true_type { }; #define HAS_MEMBER(C, member) \ has_ ## member::value 

Quale potrebbe essere usato in questo modo:

 using namespace std; struct X { int id; }; struct Y { int foo; }; DEFINE_MEMBER_CHECKER(foo) int main() { cout << boolalpha; cout << HAS_MEMBER(X, foo) << endl; cout << HAS_MEMBER(Y, foo) << endl; } 

AGGIORNAMENTO: recentemente ho fatto di più con il codice che ho postato nella mia risposta originale, quindi sto aggiornando questo per tener conto di cambiamenti / aggiunte.

Ecco alcuni frammenti di utilizzo: * Il coraggio di tutto questo è più basso

Controlla il membro x in una determinata class. Potrebbe essere var, func, class, union o enum:

 CREATE_MEMBER_CHECK(x); bool has_x = has_member_x::value; 

Controlla la funzione membro void x() :

 //Func signature MUST have T as template variable here... simpler this way :\ CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x); bool has_func_sig_void__x = has_member_func_void__x::value; 

Controlla la variabile membro x :

 CREATE_MEMBER_VAR_CHECK(x); bool has_var_x = has_member_var_x::value; 

Verifica la class membro x :

 CREATE_MEMBER_CLASS_CHECK(x); bool has_class_x = has_member_class_x::value; 

Verifica l’unione dei membri x :

 CREATE_MEMBER_UNION_CHECK(x); bool has_union_x = has_member_union_x::value; 

Controlla l’enum membro x :

 CREATE_MEMBER_ENUM_CHECK(x); bool has_enum_x = has_member_enum_x::value; 

Verifica la presenza di qualsiasi funzione membro x indipendentemente dalla firma:

 CREATE_MEMBER_CHECK(x); CREATE_MEMBER_VAR_CHECK(x); CREATE_MEMBER_CLASS_CHECK(x); CREATE_MEMBER_UNION_CHECK(x); CREATE_MEMBER_ENUM_CHECK(x); CREATE_MEMBER_FUNC_CHECK(x); bool has_any_func_x = has_member_func_x::value; 

O

 CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above. bool has_any_func_x = has_member_func_x::value; 

Dettagli e core:

 /* - Multiple inheritance forces ambiguity of member names. - SFINAE is used to make aliases to member names. - Expression SFINAE is used in just one generic has_member that can accept any alias we pass it. */ template  struct ambiguate : public Args... {}; template struct got_type : std::false_type {}; template struct got_type : std::true_type { typedef A type; }; template struct sig_check : std::true_type {}; template struct has_member { template static char ((&f(decltype(&C::value))))[1]; template static char ((&f(...)))[2]; //Make sure the member name is consistently spelled the same. static_assert( (sizeof(f(0)) == 1) , "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified." ); static bool const value = sizeof(f(0)) == 2; }; 

Macro (El Diablo!):

CREATE_MEMBER_CHECK:

 //Check for any member with given name, whether var, func, class, union, enum. #define CREATE_MEMBER_CHECK(member) \ \ template \ struct Alias_##member; \ \ template \ struct Alias_##member < \ T, std::integral_constant::value> \ > { static const decltype(&T::member) value; }; \ \ struct AmbiguitySeed_##member { char member; }; \ \ template \ struct has_member_##member { \ static const bool value \ = has_member< \ Alias_##member> \ , Alias_##member \ >::value \ ; \ } 

CREATE_MEMBER_VAR_CHECK:

 //Check for member variable with given name. #define CREATE_MEMBER_VAR_CHECK(var_name) \ \ template \ struct has_member_var_##var_name : std::false_type {}; \ \ template \ struct has_member_var_##var_name< \ T \ , std::integral_constant< \ bool \ , !std::is_member_function_pointer::value \ > \ > : std::true_type {} 

CREATE_MEMBER_FUNC_SIG_CHECK:

 //Check for member function with given name AND signature. #define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \ \ template \ struct has_member_func_##templ_postfix : std::false_type {}; \ \ template \ struct has_member_func_##templ_postfix< \ T, std::integral_constant< \ bool \ , sig_check::value \ > \ > : std::true_type {} 

CREATE_MEMBER_CLASS_CHECK:

 //Check for member class with given name. #define CREATE_MEMBER_CLASS_CHECK(class_name) \ \ template \ struct has_member_class_##class_name : std::false_type {}; \ \ template \ struct has_member_class_##class_name< \ T \ , std::integral_constant< \ bool \ , std::is_class< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_UNION_CHECK:

 //Check for member union with given name. #define CREATE_MEMBER_UNION_CHECK(union_name) \ \ template \ struct has_member_union_##union_name : std::false_type {}; \ \ template \ struct has_member_union_##union_name< \ T \ , std::integral_constant< \ bool \ , std::is_union< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_ENUM_CHECK:

 //Check for member enum with given name. #define CREATE_MEMBER_ENUM_CHECK(enum_name) \ \ template \ struct has_member_enum_##enum_name : std::false_type {}; \ \ template \ struct has_member_enum_##enum_name< \ T \ , std::integral_constant< \ bool \ , std::is_enum< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_FUNC_CHECK:

 //Check for function with given name, any signature. #define CREATE_MEMBER_FUNC_CHECK(func) \ template \ struct has_member_func_##func { \ static const bool value \ = has_member_##func::value \ && !has_member_var_##func::value \ && !has_member_class_##func::value \ && !has_member_union_##func::value \ && !has_member_enum_##func::value \ ; \ } 

CREATE_MEMBER_CHECKS:

 //Create all the checks for one member. Does NOT include func sig checks. #define CREATE_MEMBER_CHECKS(member) \ CREATE_MEMBER_CHECK(member); \ CREATE_MEMBER_VAR_CHECK(member); \ CREATE_MEMBER_CLASS_CHECK(member); \ CREATE_MEMBER_UNION_CHECK(member); \ CREATE_MEMBER_ENUM_CHECK(member); \ CREATE_MEMBER_FUNC_CHECK(member) 

Boost.ConceptTraits fornisce tra l’altro alcune macro per definire i tratti del tipo, come ad esempio BOOST_TT_EXT_DEFINE_HAS_MEMBER(name) , che definisce un tipo di tratto del modulo:

 has_member_##name 

Questo dà true se T ha un tipo di membro chiamato. Notare, tuttavia, che questo non rileva i membri del tipo di riferimento.

Nel tuo caso sarà sufficiente aggiungere un file di intestazione

 BOOST_TT_EXT_DEFINE_HAS_MEMBER_TYPE(x) 

e controllare come segue

 BOOST_STATIC_ASSERT(has_member_x::value); 

La tecnica utilizzata è la stessa di quella spiegata in alcune delle risposte precedenti.

Sfortunatamente questa libreria non è più mantenuta. Ora che C ++ 0x non include il concetto, questa libreria insieme a SFINAE è un sostituto perfetto per lavorare con la maggior parte dei concetti.

Perché non usi una specializzazione come questa:

 struct P1 {int x; }; struct P2 {int X; }; template bool Check_x(P p) { return true; } template<> bool Check_x(P2 p) { return false; } 

La seconda risposta (litb) a questo mostra come rilevare un membro:

È ansible scrivere un modello per verificare l’esistenza di una funzione?

Perché non crei solo le specializzazioni template di Check_x?

 template<> bool Check_x(P1 p) { return true; } template<> bool Check_x(P2 p) { return false; } 

Diamine, quando ci penso. Se hai solo due tipi, perché hai bisogno di modelli per questo?

Le funzioni (x, X, y, Y) di una class di base astratta, o potrebbero essere refactored per essere così? In tal caso, puoi utilizzare la macro SUPERSUBCLASS () di Modern C ++ Design, insieme alle idee tratte dalla risposta a questa domanda:

Invio basato sul tipo di compilazione

Possiamo ottenere in fase di compilazione: 0 - not_member, 1 - is_object, 2 - is_function per ogni class richiesta e membro – object o funzione: http://ideone.com/Fjm9u5

 #include  #include  #define IS_MEMBER(T1, M) \ struct { \ struct verystrangename1 { bool M; }; \ template struct verystrangename2 : verystrangename1, public T { }; \ \ enum return_t { not_member, is_object, is_function }; \ template::M)> constexpr return_t what_member() { return not_member; } \ template typename std::enable_if::value, return_t>::type constexpr what_member() { return is_object; } \ template typename std::enable_if::value, return_t>::type constexpr what_member() { return is_function; } \ constexpr operator return_t() { return what_member(); } \ } struct t { int aaa; float bbb; void func() {} }; // Can't be in function IS_MEMBER(t, aaa) is_aaa_member_of_t; IS_MEMBER(t, ccc) is_ccc_member_of_t; IS_MEMBER(t, func) is_func_member_of_t; // known at compile time enum { const_is_aaa_member_of_t = (int)is_aaa_member_of_t }; static constexpr int const_is_func_member_of_t = is_func_member_of_t; int main() { std::cout << std::boolalpha << "0 - not_member, 1 - is_object, 2 - is_function \n\n" << "is aaa member of t = " << is_aaa_member_of_t << std::endl << "is ccc member of t = " << is_ccc_member_of_t << std::endl << "is func member of t = " << is_func_member_of_t << std::endl << std::endl; return 0; } 

Risultato:

 0 - not_member, 1 - is_object, 2 - is_function is aaa member of t = 1 is ccc member of t = 0 is func member of t = 2 

Per class / struct:

 struct t { int aaa; float bbb; void func() {} };