wake-up-neo.com

Wie kann ich feststellen, ob eine bestimmte Membervariable in der Klasse vorhanden ist?

Um eine Algorithmus-Template-Funktion zu erstellen, muss ich wissen, ob x oder X (und y oder Y) in der Klasse ein Template-Argument ist. Es kann nützlich sein, wenn Sie meine Funktion für die MFC-Klasse CPoint oder die Klasse GDI + PointF oder einige andere verwenden. Alle verwenden unterschiedliche x in ihnen. Meine Lösung könnte auf folgenden Code reduziert werden:


template<int> struct TT {typedef int type;};
template<class P> bool Check_x(P p, typename TT<sizeof(&P::x)>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<sizeof(&P::X)>::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;
}

Es wird jedoch nicht in Visual Studio kompiliert, während in C++ GNU kompiliert wird. Mit Visual Studio konnte ich die folgende Vorlage verwenden:


template<class P> bool Check_x(P p, typename TT<&P::x==&P::x>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<&P::X==&P::X>::type b = 0) { return false; }

Es wird jedoch nicht in GNU C++ kompiliert. Gibt es eine universelle Lösung?

UPD: Die Strukturen P1 und P2 dienen hier nur als Beispiel. Es kann Klassen mit unbekannten Mitgliedern geben.

P.S. Bitte posten Sie keine C++ 11-Lösungen hier, da sie offensichtlich und für die Frage nicht relevant sind.

54

Ein anderer Weg ist dieser, der auf SFINAE für Ausdrücke beruht. Wenn die Namenssuche zu Mehrdeutigkeiten führt, lehnt der Compiler die Vorlage ab

template<typename T> struct HasX { 
    struct Fallback { int x; }; // introduce member name "x"
    struct Derived : T, Fallback { };

    template<typename C, C> struct ChT; 

    template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1]; 
    template<typename C> static char (&f(...))[2]; 

    static bool const value = sizeof(f<Derived>(0)) == 2;
}; 

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

int main() { 
    std::cout << HasX<A>::value << std::endl; // 1
    std::cout << HasX<B>::value << std::endl; // 0
}

Es basiert auf einer genialen Idee von jemandem im Usenet.

Hinweis: HasX sucht nach beliebigen Daten oder Funktionsmitgliedern namens x mit einem beliebigen Typ. Der einzige Zweck der Einführung des Mitgliedsnamens besteht darin, eine mögliche Mehrdeutigkeit bei der Suche nach Mitgliedsnamen zu haben. 

Hier ist eine Lösung einfacher als Johannes Schaub - litb 's one . Es benötigt C++ 11.

#include <type_traits>

template <typename T, typename = int>
struct HasX : std::false_type { };

template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };

Update : Ein kurzes Beispiel und die Erklärung, wie das funktioniert.

Für diese Typen:

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

wir haben HasX<A>::value == true und HasX<B>::value == false. Mal sehen warum.

Denken Sie zunächst daran, dass std::false_type und std::true_type ein static constexpr bool-Mitglied mit dem Namen value haben, das auf false bzw. true festgelegt ist. Daher erben die beiden oben genannten Vorlagen HasX diesen Member. (Die erste Vorlage von std::false_type und die zweite von std::true_type.)

Beginnen wir einfach und fahren dann Schritt für Schritt fort, bis wir zum obigen Code gelangen.

1) Ausgangspunkt:

template <typename T, typename U>
struct HasX : std::false_type { };

In diesem Fall ist es keine Überraschung: HasX leitet sich von std::false_type und damit von HasX<bool, double>::value == false und HasX<bool, int>::value == false ab.

2) Standardmäßig U:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

Vorausgesetzt, dass U standardmäßig int ist, bedeutet Has<bool> tatsächlich HasX<bool, int> und damit HasX<bool>::value == HasX<bool, int>::value == false.

3) Hinzufügen einer Spezialisierung:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX<T, int> : std::true_type { };

Im Allgemeinen leitet sich HasX<T, U> dank der primären Vorlage von std::false_type ab. Es gibt jedoch eine Spezialisierung für U = int, die sich von std::true_type ableitet. Daher HasX<bool, double>::value == false aber HasX<bool, int>::value == true.

Dank der Standardeinstellung für U, HasX<bool>::value == HasX<bool, int>::value == true.

4) decltype und eine ausgefallene Art, int zu sagen:

Ein kleiner Exkurs hier, aber bitte, nehmen Sie mich mit.

Grundsätzlich (das ist nicht ganz richtig) liefert decltype(expression) den Typ des Ausdrucks . Beispielsweise hat 0 den Typ int, dh decltype(0) bedeutet int. In analoger Weise hat 1.2 den Typ double, und daher bedeutet decltype(1.2)double.

Betrachten Sie eine Funktion mit dieser Deklaration:

char func(foo, int);

dabei ist foo ein Klassentyp. Wenn f ein Objekt vom Typ foo ist, bedeutet decltype(func(f, 0))char (der von func(f, 0) zurückgegebene Typ).

Jetzt verwendet der Ausdruck (1.2, 0) den (eingebauten) Kommaoperator, der die beiden Unterausdrücke nacheinander auswertet (dh zuerst 1.2 und dann 0), den ersten Wert verwirft und den zweiten ergibt. Daher,

int x = (1.2, 0);

ist äquivalent zu

int x = 0;

Zusammen mit decltype bedeutet decltype(1.2, 0)int. An 1.2 oder double ist nichts wirklich Besonderes. Beispielsweise hat true den Typ bool und decltype(true, 0) bedeutet int ebenfalls.

Was ist mit einem Klassentyp? Was bedeutet zum Beispiel decltype(f, 0)? Es ist natürlich zu erwarten, dass dies immer noch int bedeutet, aber es kann sein, dass dies nicht der Fall ist. In der Tat kann es zu einer Überladung des Komma-Operators kommen, ähnlich der Funktion func, die ein foo und ein int annimmt und ein char zurückgibt. In diesem Fall ist decltype(foo, 0)char.

Wie können wir die Verwendung einer Überladung für den Kommaoperator vermeiden? Nun, es gibt keine Möglichkeit, den Kommaoperator für einen void -Operanden zu überladen, und wir können alles in void umwandeln. Daher bedeutet decltype((void) f, 0)int. In der Tat wandelt (void) ff von foo nach void, was im Grunde nichts anderes tut, als zu sagen, dass der Ausdruck den Typ void haben muss. Dann wird das eingebaute Operator-Komma verwendet und ((void) f, 0) ergibt 0 mit dem Typ int. Daher bedeutet decltype((void) f, 0)int.

Ist diese Besetzung wirklich notwendig? Nun, wenn es keine Überlastung für den Kommaoperator gibt, der foo und int nimmt, dann ist dies nicht notwendig. Wir können den Quellcode jederzeit überprüfen, ob es einen solchen Operator gibt oder nicht. Wenn dies jedoch in einer Vorlage erscheint und f den Typ V hat, der ein Vorlagenparameter ist, ist es nicht mehr klar (oder sogar unmöglich zu wissen), ob eine solche Überladung für den Kommaoperator vorliegt oder nicht. Um generisch zu sein, haben wir trotzdem gegossen.

Fazit: decltype((void) f, 0) ist eine ausgefallene Art, int zu sagen.

5) SFINAE:

Das ist eine ganze Wissenschaft ;-) OK, ich übertreibe, aber es ist auch nicht sehr einfach. Also werde ich die Erklärung auf das Nötigste beschränken.

SFINAE steht für Substitution Failure is Not An Error. Dies bedeutet, dass, wenn ein Vorlagenparameter durch einen Typ ersetzt wird, möglicherweise ein unzulässiger C++ - Code angezeigt wird, der Compiler den anstößigen Code jedoch einfach ignoriert , anstatt die Kompilierung abzubrechen es war nicht da Mal sehen, wie es auf unseren Fall zutrifft:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };

Auch hier ist decltype((void) T::x, 0) eine originelle Art, int zu sagen, aber mit dem Vorteil von SFINAE.

Wenn T durch einen Typ ersetzt wird, wird möglicherweise ein ungültiges Konstrukt angezeigt. Zum Beispiel ist bool::x kein gültiges C++. Wenn Sie also T durch bool in T::x ersetzen, erhalten Sie ein ungültiges Konstrukt. Nach dem SFINAE-Prinzip lehnt der Compiler den Code nicht ab, sondern ignoriert ihn (teilweise). Genauer gesagt, wie wir gesehen haben, bedeutet HasX<bool> tatsächlich HasX<bool, int>. Die Spezialisierung für U = int sollte ausgewählt werden, aber beim Instanziieren findet der Compiler bool::x und ignoriert die Template-Spezialisierung vollständig, als ob sie nicht existiert hätte.

Zu diesem Zeitpunkt ist der Code im Wesentlichen derselbe wie in Fall (2) oben, in dem nur die primäre Vorlage vorhanden ist. Daher HasX<bool, int>::value == false.

Dasselbe Argument, das für bool verwendet wird, gilt für B, da B::x ein ungültiges Konstrukt ist (B hat kein Mitglied x). A::x ist jedoch in Ordnung, und der Compiler kann die Spezialisierung für U = int (oder genauer gesagt für U = decltype((void) A::x, 0)) problemlos instanziieren. Daher HasX<A>::value == true.

6) Unnaming U:

Wenn wir uns den Code in (5) noch einmal ansehen, sehen wir, dass der Name U nur in der Deklaration (typename U) verwendet wird. Wir können dann das zweite Template-Argument unbenennen und erhalten den Code, der oben in diesem Beitrag angezeigt wird.

79
Cassio Neri

Ich wurde hier von einem Frage umgeleitet, das als Duplikat dieses abgeschlossen wurde. Ich weiß, dass es ein alter Thread ist, aber ich wollte nur eine alternative (einfachere?) Implementierung vorschlagen, die mit C++ 11 funktioniert. Angenommen, wir wollen prüfen, ob eine bestimmte Klasse eine Membervariable namens id hat:

#include <type_traits>

template<typename T, typename = void>
struct has_id : std::false_type { };

template<typename T>
struct has_id<T, decltype(std::declval<T>().id, void())> : std::true_type { };

Das ist es. Und so würde es verwendet werden ( live example ):

#include <iostream>

using namespace std;

struct X { int id; };
struct Y { int foo; };

int main()
{
    cout << boolalpha;
    cout << has_id<X>::value << endl;
    cout << has_id<Y>::value << endl;
}

Mit ein paar Makros können die Dinge noch einfacher gestaltet werden:

#define DEFINE_MEMBER_CHECKER(member) \
    template<typename T, typename V = bool> \
    struct has_ ## member : false_type { }; \
    template<typename T> \
    struct has_ ## member<T, \
        typename enable_if< \
            !is_same<decltype(declval<T>().member), void>::value, \
            bool \
            >::type \
        > : true_type { };

#define HAS_MEMBER(C, member) \
    has_ ## member<C>::value

Welches könnte so verwendet werden:

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;
}
28
Andy Prowl

UPDATE: Ich habe kürzlich etwas mehr mit dem Code getan, den ich in meiner ursprünglichen Antwort gepostet habe. Daher aktualisiere ich dies, um Änderungen/Ergänzungen zu berücksichtigen.

Hier sind einige Verwendungsausschnitte: * Der Mut dazu ist weiter unten

Suche nach Member x in einer gegebenen Klasse. Könnte var, func, class, union oder enum sein:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

Auf Memberfunktion void x() prüfen:

//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<class_to_check_for_x>::value;

Auf Membervariable x prüfen:

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

Auf Mitgliedsklasse x prüfen:

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

Auf Mitgliedsvereinigung prüfen x:

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

Auf Mitglieder-Enumeration x prüfen:

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

Suche nach einer beliebigen Mitgliedsfunktion x unabhängig von der Signatur:

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<class_to_check_for_x>::value;

OR

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

Details und Kern:

/*
    - 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 <typename... Args> struct ambiguate : public Args... {};

template<typename A, typename = void>
struct got_type : std::false_type {};

template<typename A>
struct got_type<A> : std::true_type {
    typedef A type;
};

template<typename T, T>
struct sig_check : std::true_type {};

template<typename Alias, typename AmbiguitySeed>
struct has_member {
    template<typename C> static char ((&f(decltype(&C::value))))[1];
    template<typename C> static char ((&f(...)))[2];

    //Make sure the member name is consistently spelled the same.
    static_assert(
        (sizeof(f<AmbiguitySeed>(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<Alias>(0)) == 2;
};

Makros (El Diablo!):

CREATE_MEMBER_CHECK:

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

CREATE_MEMBER_VAR_CHECK:

//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name)                                   \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_var_##var_name : std::false_type {};                      \
                                                                            \
template<typename T>                                                        \
struct has_member_var_##var_name<                                           \
    T                                                                       \
    , std::integral_constant<                                               \
        bool                                                                \
        , !std::is_member_function_pointer<decltype(&T::var_name)>::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<typename T, typename = std::true_type>                             \
struct has_member_func_##templ_postfix : std::false_type {};                \
                                                                            \
template<typename T>                                                        \
struct has_member_func_##templ_postfix<                                     \
    T, std::integral_constant<                                              \
        bool                                                                \
        , sig_check<func_sig, &T::func_name>::value                         \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_CLASS_CHECK:

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

CREATE_MEMBER_UNION_CHECK:

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

CREATE_MEMBER_ENUM_CHECK:

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

CREATE_MEMBER_FUNC_CHECK:

//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func)          \
template<typename T>                            \
struct has_member_func_##func {                 \
    static const bool value                     \
        = has_member_##func<T>::value           \
        && !has_member_var_##func<T>::value     \
        && !has_member_class_##func<T>::value   \
        && !has_member_union_##func<T>::value   \
        && !has_member_enum_##func<T>::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)
7
Brett Rossier

Boost.ConceptTraits stellt zwischen anderen Makros bereit, um Typmerkmale zu definieren, wie zum Beispiel BOOST_TT_EXT_DEFINE_HAS_MEMBER(name), die ein Typmerkmal des Formulars definieren:

has_member_##name<T>

Dies ergibt "true", wenn für T ein Mitgliedstyp angegeben wurde. Beachten Sie jedoch, dass dies keine Referenztypmitglieder erkennt.

In Ihrem Fall reicht es aus, eine Header-Datei hinzuzufügen

BOOST_TT_EXT_DEFINE_HAS_MEMBER_TYPE(x)

und prüfen Sie wie folgt

BOOST_STATIC_ASSERT(has_member_x<P1>::value);

Die verwendete Technik ist dieselbe wie bei einigen der vorhergehenden Antworten.

Leider wird diese Bibliothek nicht mehr unterhalten. Da C++ 0x jetzt kein Konzept enthält, ist diese Bibliothek zusammen mit SFINAE ein idealer Ersatz für die meisten Konzepte.

Die zweite Antwort (litb's) zeigt, wie man ein Mitglied erkennt:

Ist es möglich, eine Vorlage zu schreiben, um die Existenz einer Funktion zu prüfen?

2
James Hopkin

Warum nutzen Sie die Spezialisierung nicht so:

struct P1 {int x; };
struct P2 {int X; };

template<class P> 
bool Check_x(P p) { return true; }

template<> 
bool Check_x<P2>(P2 p) { return false; }
2
Naveen

Warum erstellen Sie nicht einfach Template-Spezialisierungen von Check_x?

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

Heck, wenn ich daran denke. Wenn Sie nur zwei Typen haben, warum brauchen Sie dafür sogar Vorlagen?

1
ralphtheninja

Handelt es sich bei den Funktionen (x, X, y, Y) um eine abstrakte Basisklasse oder könnten sie umgestaltet werden? Wenn ja, können Sie das SUPERSUBCLASS () -Makro von Modern C++ Design zusammen mit Ideen aus der Antwort auf diese Frage verwenden:

Typ-basierter Versand bei der Kompilierung

1
user23167

Wir können zur Kompilierzeit erhalten: 0 - not_member, 1 - is_object, 2 - is_function für jede erforderliche Klasse und Member - Objekt oder Funktion: http://ideone.com/Fjm9u5

#include <iostream>
#include <type_traits>

#define IS_MEMBER(T1, M)    \
struct {        \
    struct verystrangename1 { bool M; };    \
    template<typename T> struct verystrangename2 : verystrangename1, public T { }; \
    \
    enum return_t { not_member, is_object, is_function }; \
    template<typename T, typename = decltype(verystrangename2<T>::M)> constexpr return_t what_member() { return not_member;  }  \
    template<typename T> typename std::enable_if<std::is_member_object_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() { return is_object; }   \
    template<typename T> typename std::enable_if<std::is_member_function_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() { return is_function; }   \
    constexpr operator return_t() { return what_member<T1>(); } \
}

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;
}

Ergebnis:

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

Für Klasse/Struktur:

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