it-swarm-eu.dev

Überprüfen Sie, ob eine Klasse eine Mitgliedsfunktion für eine bestimmte Signatur hat

Ich bitte um einen Vorlagentrick, um festzustellen, ob eine Klasse eine bestimmte Elementfunktion einer bestimmten Signatur hat.

Das Problem ähnelt dem hier zitierten http://www.gotw.ca/gotw/071.htm aber nicht dasselbe: In dem Punkt von Sutters Buch beantwortete er die Frage, dass eine Klasse C MUSS eine Member-Funktion mit einer bestimmten Signatur versehen, sonst wird das Programm nicht kompiliert. In meinem Problem muss ich etwas tun, wenn eine Klasse diese Funktion hat, sonst "etwas anderes".

Ein ähnliches Problem gab es bei boost :: serialization, aber die von ihnen gewählte Lösung gefällt mir nicht: Eine Template-Funktion, die standardmäßig eine freie Funktion (die Sie definieren müssen) mit einer bestimmten Signatur aufruft, es sei denn, Sie definieren eine bestimmte Member-Funktion ( In ihrem Fall "serialize", das 2 Parameter eines bestimmten Typs mit einer bestimmten Signatur verwendet, tritt andernfalls ein Kompilierungsfehler auf. Das heißt, sowohl intrusive als auch nicht intrusive Serialisierung zu implementieren.

Diese Lösung gefällt mir aus zwei Gründen nicht:

  1. Um nicht aufdringlich zu sein, müssen Sie die globale Funktion "serialize" im Namespace boost :: serialization überschreiben. Sie müssen also IN IHREM CLIENT-CODE den Namespace boost und die Namespace-Serialisierung öffnen!
  2. Der Stapel, um dieses Durcheinander aufzulösen, bestand aus 10 bis 12 Funktionsaufrufen.

Ich muss ein benutzerdefiniertes Verhalten für Klassen definieren, die nicht über diese Elementfunktion verfügen, und meine Entitäten befinden sich in verschiedenen Namespaces (und ich möchte keine globale Funktion überschreiben, die in einem Namespace definiert ist, während ich mich in einem anderen befinde).

Können Sie mir einen Hinweis geben, um dieses Rätsel zu lösen?

121
ugasoft

Ich bin nicht sicher, ob ich Sie richtig verstehe, aber Sie können SFINAE ausnutzen, um das Vorhandensein von Funktionen beim Kompilieren zu erkennen. Beispiel aus meinem Code (testet, ob die Klasse die Member-Funktion size_t used_memory () const hat).

template<typename T>
struct HasUsedMemoryMethod
{
    template<typename U, size_t (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
    template<typename U> static int Test(...);
    static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};

template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
        // We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
    ReportMemUsage(m, 
        std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}
87
yrp

Hier ist eine mögliche Implementierung, die sich auf C++ 11-Funktionen stützt. Es erkennt die Funktion korrekt, auch wenn sie vererbt wurde (im Gegensatz zu der Lösung in der akzeptierten Antwort, wie Mike Kinghan in seiner Antwort ).

Die Funktion, auf die dieses Snippet testet, heißt serialize:

#include <type_traits>

// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.

template<typename, typename T>
struct has_serialize {
    static_assert(
        std::integral_constant<T, false>::value,
        "Second template parameter needs to be of function type.");
};

// specialization that does the checking

template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
    template<typename T>
    static constexpr auto check(T*)
    -> typename
        std::is_same<
            decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
            Ret    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        >::type;  // attempt to call it and see if the return type is correct

    template<typename>
    static constexpr std::false_type check(...);

    typedef decltype(check<C>(0)) type;

public:
    static constexpr bool value = type::value;
};

Verwendung:

struct X {
     int serialize(const std::string&) { return 42; } 
};

struct Y : X {};

std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1
119
jrok

Die akzeptierte Antwort auf diese Frage der Introspektion von Compiletime-Member-Funktionen, obwohl sie zu Recht populär ist, hat einen Haken, der im folgenden Programm beobachtet werden kann:

#include <type_traits>
#include <iostream>
#include <memory>

/*  Here we apply the accepted answer's technique to probe for the
    the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
    template<typename U, E (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::operator*>*);
    template<typename U> static int Test(...);
    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};

using namespace std;

/* Here we test the `std::` smart pointer templates, including the
    deprecated `auto_ptr<T>`, to determine in each case whether
    T = (the template instantiated for `int`) provides 
    `int & T::operator*() const` - which all of them in fact do.
*/ 
int main(void)
{
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
    return 0;
}

Das mit GCC 4.6.3 erstellte Programm gibt 110 Aus und teilt uns mit, dass T = std::shared_ptr<int> nichtint & T::operator*() const bereitstellt.

Wenn Sie mit diesem Thema noch nicht vertraut sind, wird ein Blick auf die Definition von std::shared_ptr<T> In der Kopfzeile <memory> Aufschluss geben. In dieser Implementierung wird std::shared_ptr<T> Von einer Basisklasse abgeleitet, von der es operator*() const erbt. Die Template-Instanziierung SFINAE<U, &U::operator*>, Die das "Finden" des Operators für U = std::shared_ptr<T> Darstellt, wird daher nicht durchgeführt, da std::shared_ptr<T> Kein operator*() für sich und Template hat Die Instanziierung "macht keine Vererbung".

Dieser Haken wirkt sich nicht auf den bekannten SFINAE-Ansatz aus, der "The sizeof () Trick" verwendet, um lediglich zu ermitteln, ob T eine Mitgliedsfunktion mf hat (siehe z. B. diese Antwort) und Kommentare). Die Feststellung, dass T::mf Existiert, ist jedoch häufig (normalerweise?) Nicht gut genug: Möglicherweise müssen Sie auch feststellen, dass die gewünschte Signatur vorliegt. Hier punktet die abgebildete Technik. Die mit Zeigern versehene Variante der gewünschten Signatur ist in einem Parameter eines Vorlagentyps eingetragen, der mit &T::mf Erfüllt sein muss, damit die SFINAE-Sonde erfolgreich ist. Diese Template-Instanziierungstechnik gibt jedoch die falsche Antwort, wenn T::mf Geerbt wird.

Bei einer sicheren SFINAE-Technik zur Überwachung von T::mf Auf Kompilationszeit muss die Verwendung von &T::mf In einem Vorlagenargument vermieden werden, um einen Typ zu instanziieren, von dem die Auflösung von SFINAE-Funktionsvorlagen abhängt. Stattdessen kann die Auflösung der SFINAE-Vorlagenfunktion nur von genau relevanten Typdeklarationen abhängen, die als Argumenttypen der überladenen SFINAE-Testfunktion verwendet werden.

Als Antwort auf die Frage, die diese Einschränkung einhält, zeige ich die Ermittlung der Kompilierzeit für E T::operator*() const, für beliebige T und E. Das gleiche Muster gilt mutatis mutandis, um nach einer anderen Signatur einer Mitgliedsmethode zu suchen.

#include <type_traits>

/*! The template `has_const_reference_op<T,E>` exports a
    boolean constant `value that is true iff `T` provides
    `E T::operator*() const`
*/ 
template< typename T, typename E>
struct has_const_reference_op
{
    /* SFINAE operator-has-correct-sig :) */
    template<typename A>
    static std::true_type test(E (A::*)() const) {
        return std::true_type();
    }

    /* SFINAE operator-exists :) */
    template <typename A> 
    static decltype(test(&A::operator*)) 
    test(decltype(&A::operator*),void *) {
        /* Operator exists. What about sig? */
        typedef decltype(test(&A::operator*)) return_type; 
        return return_type();
    }

    /* SFINAE game over :( */
    template<typename A>
    static std::false_type test(...) {
        return std::false_type(); 
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<T>(0,0)) type;

    static const bool value = type::value; /* Which is it? */
};

In dieser Lösung wird die überladene SFINAE-Testfunktion test() "rekursiv aufgerufen". (Natürlich wird es überhaupt nicht aufgerufen; es enthält lediglich die vom Compiler aufgelösten Rückgabetypen für hypothetische Aufrufe.)

Wir müssen nach mindestens einem und höchstens zwei Informationspunkten suchen:

  • Gibt es T::operator*() überhaupt? Wenn nicht, sind wir fertig.
  • Wenn T::operator*() existiert, ist seine Signatur E T::operator*() const?

Wir erhalten die Antworten, indem wir den Rückgabetyp eines einzelnen Aufrufs von test(0,0) auswerten. Das machen:

    typedef decltype(test<T>(0,0)) type;

Dieser Aufruf wird möglicherweise in die Überladung /* SFINAE operator-exists :) */ Von test() oder in die Überladung /* SFINAE game over :( */ Aufgelöst. Es kann nicht zur /* SFINAE operator-has-correct-sig :) */ - Überladung aufgelöst werden, da diese nur ein Argument erwartet und wir zwei übergeben.

Warum kommen wir an zwei vorbei? Einfach, um die Auflösung auszuschließen /* SFINAE operator-has-correct-sig :) */. Das zweite Argument hat keine andere Bedeutung.

Dieser Aufruf von test(0,0) wird in /* SFINAE operator-exists :) */ Aufgelöst, falls das erste Argument 0 den ersten Parametertyp dieser Überladung, nämlich decltype(&A::operator*), mit A = T. 0 erfüllt diesen Typ nur für den Fall, dass T::operator* Existiert.

Nehmen wir an, der Compiler sagt Ja dazu. Dann geht es mit /* SFINAE operator-exists :) */ Und es muss der Rückgabetyp des Funktionsaufrufs bestimmt werden, der in diesem Fall decltype(test(&A::operator*)) ist - der Rückgabetyp eines weiteren Aufrufs von test().

Dieses Mal übergeben wir nur ein Argument, &A::operator*, Von dem wir jetzt wissen, dass es existiert, oder wir wären nicht hier. Ein Aufruf von test(&A::operator*) kann entweder zu /* SFINAE operator-has-correct-sig :) */ Oder erneut zu /* SFINAE game over :( */ Aufgelöst werden. Der Aufruf wird mit /* SFINAE operator-has-correct-sig :) */ Übereinstimmen, falls &A::operator* Den einzelnen Parametertyp dieser Überladung (E (A::*)() const) mit A = T Erfüllt.

Der Compiler sagt hier Ja, wenn T::operator* Die gewünschte Signatur hat, und muss dann erneut den Rückgabetyp der Überladung auswerten. Keine "Rekursionen" mehr: Es ist std::true_type.

Wenn der Compiler nicht /* SFINAE operator-exists :) */ Für den Aufruf test(0,0) wählt oder nicht /* SFINAE operator-has-correct-sig :) */ Für den Aufruf test(&A::operator*) wählt, geht es in beiden Fällen mit /* SFINAE game over :( */ Und der endgültige Rückgabetyp ist std::false_type.

Hier ist ein Testprogramm, das die Vorlage zeigt, die die erwarteten Antworten in verschiedenen Fallbeispielen liefert (wieder GCC 4.6.3).

// To test
struct empty{};

// To test 
struct int_ref
{
    int & operator*() const {
        return *_pint;
    }
    int & foo() const {
        return *_pint;
    }
    int * _pint;
};

// To test 
struct sub_int_ref : int_ref{};

// To test 
template<typename E>
struct ee_ref
{
    E & operator*() {
        return *_pe;
    }
    E & foo() const {
        return *_pe;
    }
    E * _pe;
};

// To test 
struct sub_ee_ref : ee_ref<char>{};

using namespace std;

#include <iostream>
#include <memory>
#include <vector>

int main(void)
{
    cout << "Expect Yes" << endl;
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value;
    cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
    cout << has_const_reference_op<std::vector<int>::const_iterator,
            int const &>::value;
    cout << has_const_reference_op<int_ref,int &>::value;
    cout << has_const_reference_op<sub_int_ref,int &>::value  << endl;
    cout << "Expect No" << endl;
    cout << has_const_reference_op<int *,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,char &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int>::value;
    cout << has_const_reference_op<unique_ptr<long>,int &>::value;
    cout << has_const_reference_op<int,int>::value;
    cout << has_const_reference_op<std::vector<int>,int &>::value;
    cout << has_const_reference_op<ee_ref<int>,int &>::value;
    cout << has_const_reference_op<sub_ee_ref,int &>::value;
    cout << has_const_reference_op<empty,int &>::value  << endl;
    return 0;
}

Gibt es neue Mängel in dieser Idee? Kann es allgemeiner gestaltet werden, ohne erneut den Haken zu verlieren, den es vermeidet?

35
Mike Kinghan

Hier sind einige Verwendungsausschnitte: * Die Eingeweide für all dies sind weiter unten

Prüfe auf Member x in einer bestimmten Klasse. Kann var, func, class, union oder enum sein:

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

Auf Mitgliedsfunktion prüfen 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<class_to_check_for_x>::value;

Auf Mitgliedsvariable x prüfen:

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

Prüfe auf Memberklasse x:

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

Auf Mitgliedsgewerkschaft prüfen x:

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

Auf Mitgliedsnummer prüfen x:

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

Prüfen Sie, ob eine Mitgliedsfunktion x vorhanden ist, 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.
*/

//Variadic to force ambiguity of class members.  C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};

//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};

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)
13
Brett Rossier

Dies sollte ausreichen, wenn Sie den Namen der zu erwartenden Mitgliedsfunktion kennen. (In diesem Fall kann die Funktion bla nicht instanziiert werden, wenn keine Mitgliedsfunktion vorhanden ist (das Schreiben einer Funktion, die trotzdem funktioniert, ist schwierig, da eine teilweise Spezialisierung der Funktion fehlt. Möglicherweise müssen Sie Klassenvorlagen verwenden.) Auch die enable-Struktur (which ist ähnlich wie enable_if) kann auch auf die Art der Funktion abgestellt werden, die Sie als Mitglied haben möchten.

template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
  A a;
  B b;
  bla(b);
  bla(a);
}
11
coppro

Um dies zu erreichen, müssen wir verwenden:

  1. Überladen von Funktionsschablonen mit unterschiedlichen Rückgabetypen, je nachdem, ob die Methode verfügbar ist
  2. In Übereinstimmung mit den Meta-Bedingungen im Header type_traits möchten wir ein true_type Oder false_type Zurückgeben = von unseren Überladungen
  3. Deklarieren Sie die Überladung true_type Mit der Erwartung einer int und die Überladung false_type Mit der Erwartung variabler Parameter, die ausgenutzt werden sollen: "Die niedrigste Priorität der Ellipsenkonvertierung bei der Überladungsauflösung" =
  4. Bei der Definition der Vorlagenspezifikation für die Funktion true_type Werden wir declval und decltype verwenden, um die Funktion zu erkennen unabhängig von Rückgabetypunterschieden oder Überladungen zwischen Methoden

Sie können ein Live-Beispiel dafür sehen hier . Aber ich werde es auch unten erklären:

Ich möchte überprüfen, ob eine Funktion mit dem Namen test existiert, die einen konvertierbaren Typ von int annimmt. Dann müsste ich diese beiden Funktionen deklarieren:

template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
  • decltype(hasTest<a>(0))::value is true (Beachten Sie, dass keine spezielle Funktionalität erstellt werden muss, um mit der Überladung void a::test() fertig zu werden. Die Überladung void a::test(int) wird akzeptiert.)
  • decltype(hasTest<b>(0))::value ist true (Weil int in double konvertierbar ist int b::test(double) wird akzeptiert, unabhängig vom Rückgabetyp)
  • decltype(hasTest<c>(0))::value is false (c hat keine Methode mit dem Namen test, die einen von int konvertierbaren Typ akzeptiert, weshalb dies nicht akzeptiert wird )

Diese Lösung hat zwei Nachteile:

  1. Erfordert eine Methodendeklaration eines Funktionspaares
  2. Verursacht eine Namespace-Verschmutzung, insbesondere wenn auf ähnliche Namen geprüft werden soll. Wie würden wir beispielsweise eine Funktion benennen, die auf eine test() -Methode geprüft werden soll?

Daher ist es wichtig, dass diese Funktionen in einem Details-Namespace deklariert werden. Wenn sie nur mit einer Klasse verwendet werden sollen, sollten sie im Idealfall von dieser Klasse privat deklariert werden. Zu diesem Zweck habe ich ein Makro geschrieben, mit dem Sie diese Informationen zusammenfassen können:

#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
                              template <typename T> static false_type __ ## DEFINE(...); \
                              template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));

Sie könnten dies wie folgt verwenden:

namespace details {
    FOO(test(declval<int>()), test_int)
    FOO(test(), test_void)
}

Ein nachfolgender Aufruf von details::test_int<a>::value Oder details::test_void<a>::value Würde true oder false zum Zwecke des Inline-Codes oder der Metaprogrammierung ergeben.

5
Jonathan Mee

Hier ist eine einfachere Darstellung der Antwort von Mike Kinghan. Dadurch werden geerbte Methoden erkannt. Es wird auch nach der exakten Signatur gesucht (im Gegensatz zu jroks Ansatz, der Argumentkonvertierungen erlaubt).

template <class C>
class HasGreetMethod
{
    template <class T>
    static std::true_type testSignature(void (T::*)(const char*) const);

    template <class T>
    static decltype(testSignature(&T::greet)) test(std::nullptr_t);

    template <class T>
    static std::false_type test(...);

public:
    using type = decltype(test<C>(nullptr));
    static const bool value = type::value;
};

struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");

Lauffähig Beispiel

5
Valentin Milea

Sie können std :: is_member_function_pointer verwenden.

class A {
   public:
     void foo() {};
}

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
4
Yochai Timmer

Kam mit der gleichen Art von Problem selbst und fand die vorgeschlagenen Lösungen hier sehr interessant ... hatte aber die Anforderung nach einer Lösung, die:

  1. Erkennt auch geerbte Funktionen;
  2. Kompatibel mit nicht C++ 11-fähigen Compilern (also kein Decltype)

Fand ein anderes Thread , das so etwas vorschlägt, basierend auf einem BOOST-Diskussion . Hier ist die Verallgemeinerung der vorgeschlagenen Lösung als Zwei-Makros-Deklaration für die Eigenschaftsklasse nach dem Modell von boost :: has _ * Klassen.

#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>

/// Has constant function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)

/// Has non-const function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)

// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...)  \
    template                                                                  \
    <   typename Type,                                                        \
        bool is_class = boost::is_class<Type>::value                          \
    >                                                                         \
    class has_func_ ## func_name;                                             \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,false>                                  \
    {public:                                                                  \
        BOOST_STATIC_CONSTANT( bool, value = false );                         \
        typedef boost::false_type type;                                       \
    };                                                                        \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,true>                                   \
    {   struct yes { char _foo; };                                            \
        struct no { yes _foo[2]; };                                           \
        struct Fallback                                                       \
        {   func_ret_type func_name( __VA_ARGS__ )                            \
                UTILITY_OPTIONAL(func_const,const) {}                         \
        };                                                                    \
        struct Derived : public Type, public Fallback {};                     \
        template <typename T, T t>  class Helper{};                           \
        template <typename U>                                                 \
        static no deduce(U*, Helper                                           \
            <   func_ret_type (Fallback::*)( __VA_ARGS__ )                    \
                    UTILITY_OPTIONAL(func_const,const),                       \
                &U::func_name                                                 \
            >* = 0                                                            \
        );                                                                    \
        static yes deduce(...);                                               \
    public:                                                                   \
        BOOST_STATIC_CONSTANT(                                                \
            bool,                                                             \
            value = sizeof(yes)                                               \
                == sizeof( deduce( static_cast<Derived*>(0) ) )               \
        );                                                                    \
        typedef ::boost::integral_constant<bool,value> type;                  \
        BOOST_STATIC_CONSTANT(bool, is_const = func_const);                   \
        typedef func_ret_type return_type;                                    \
        typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;                \
    }

// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__

Diese Makros werden zu einer Merkmalklasse mit dem folgenden Prototyp erweitert:

template<class T>
class has_func_[func_name]
{
public:
    /// Function definition result value
    /** Tells if the tested function is defined for type T or not.
    */
    static const bool value = true | false;

    /// Function definition result type
    /** Type representing the value attribute usable in
        http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
    */
    typedef boost::integral_constant<bool,value> type;

    /// Tested function constness indicator
    /** Indicates if the tested function is const or not.
        This value is not deduced, it is forced depending
        on the user call to one of the traits generators.
    */
    static const bool is_const = true | false;

    /// Tested function return type
    /** Indicates the return type of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef func_ret_type return_type;

    /// Tested function arguments types
    /** Indicates the arguments types of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};

Was ist die typische Verwendung, die man daraus machen kann?

// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
    // Next line will declare the traits class
    // to detect the member function void foo(int,int) const
    DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}

// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>

// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   _this_.foo(a,b);
}

// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   default_foo(_this_,a,b);
}

// Let us declare test types
struct empty
{
};
struct direct_foo
{
    void foo(int,int);
};
struct direct_const_foo
{
    void foo(int,int) const;
};
struct inherited_const_foo :
    public direct_const_foo
{
};

// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
    int a;
    foo_bar(a); // calls default_foo

    empty b;
    foo_bar(b); // calls default_foo

    direct_foo c;
    foo_bar(c); // calls default_foo (member function is not const)

    direct_const_foo d;
    foo_bar(d); // calls d.foo (member function is const)

    inherited_const_foo e;
    foo_bar(e); // calls e.foo (inherited member function)
}
4
S. Paris

Um nicht aufdringlich zu sein, können Sie serialize dank Koenig lookup auch in den Namespace der serialisierten Klasse oder der Archivklasse einfügen. Weitere Informationen finden Sie unter Namespaces für Außerkraftsetzungen für freie Funktionen . :-)

Es ist einfach falsch, einen bestimmten Namespace zu öffnen, um eine freie Funktion zu implementieren. (Sie sollten beispielsweise den Namespace std nicht öffnen, um swap für Ihre eigenen Typen zu implementieren, sondern stattdessen Koenig-Lookup verwenden.)

3

Okay. Zweiter Versuch. Es ist in Ordnung, wenn dir das auch nicht gefällt, ich suche nach weiteren Ideen.

In Herb Sutters Artikel geht es um Eigenschaften. Sie können also eine Traits-Klasse haben, deren Standardinstanziierung das Fallback-Verhalten aufweist, und für jede Klasse, in der Ihre Member-Funktion vorhanden ist, ist die Traits-Klasse darauf spezialisiert, die Member-Funktion aufzurufen. Ich glaube, Herbs Artikel erwähnt eine Technik, um dies zu tun, so dass nicht viel kopiert und eingefügt werden muss.

Wie ich schon sagte, möchten Sie vielleicht nicht die zusätzliche Arbeit, die mit dem "Taggen" von Klassen verbunden ist, die dieses Mitglied implementieren. In diesem Fall suche ich eine dritte Lösung ....

2

Ohne C++ 11-Unterstützung (decltype) könnte dies funktionieren:

SSCCE

#include <iostream>
using namespace std;

struct A { void foo(void); };
struct Aa: public A { };
struct B { };

struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };

template<typename T>
struct FooFinder {
    typedef char true_type[1];
    typedef char false_type[2];

    template<int>
    struct TypeSink;

    template<class U>
    static true_type &match(U);

    template<class U>
    static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);

    template<class U>
    static false_type &test(...);

    enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};

int main() {
    cout << FooFinder<A>::value << endl;
    cout << FooFinder<Aa>::value << endl;
    cout << FooFinder<B>::value << endl;

    cout << FooFinder<retA>::value << endl;
    cout << FooFinder<argA>::value << endl;
    cout << FooFinder<constA>::value << endl;
    cout << FooFinder<varA>::value << endl;
}

Wie es hoffentlich funktioniert

A, Aa und B sind die fraglichen Klassen, wobei Aa die spezielle Klasse ist, die das gesuchte Mitglied erbt.

In FooFinder sind true_type Und false_type Die Ersetzungen für die entsprechenden C++ 11-Klassen. Auch für das Verständnis der Template-Meta-Programmierung legen sie die Grundlage des SFINAE-sizeof-Tricks offen.

Die TypeSink ist eine Template-Struktur, die später verwendet wird, um das Integralergebnis des sizeof -Operators in eine Template-Instanziierung zu versenken, um einen Typ zu bilden.

Die Funktion match ist eine weitere SFINAE-Art von Vorlage, die ohne generisches Gegenstück bleibt. Sie kann daher nur instanziiert werden, wenn der Typ ihres Arguments mit dem Typ übereinstimmt, für den sie spezialisiert wurde.

Beide Funktionen test bilden zusammen mit der Enum-Deklaration schließlich das zentrale SFINAE-Muster. Es gibt eine generische Variante mit einer Ellipse, die den false_type Und ein Gegenstück mit spezifischeren Argumenten zurückgibt, die Vorrang haben.

Um die test -Funktion mit einem Template-Argument von T instanziieren zu können, muss die match -Funktion instanziiert werden, da ihr Rückgabetyp zur Instanziierung von TypeSink Streit. Die Einschränkung besteht darin, dass &U::foo, Das in ein Funktionsargument eingeschlossen ist, nicht aus einer Spezialisierung für Vorlagenargumente heraus referenziert wird, sodass die Suche nach geerbten Mitgliedern weiterhin stattfindet.

1
Kamajii

Ich glaube, die Antwort, die Sie suchen, ist hier.

http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Compile-Time

und ein etwas ausgefüllteres Beispiel hier

http://pastie.org/298994

Ich benutze die Technik, um das Vorhandensein eines unterstützenden Ostream-Operators << für die betreffende Klasse zu erkennen und dann je nach Code ein anderes Bit zu generieren.

Ich habe nicht geglaubt, dass es möglich ist, bevor ich die verknüpfte Lösung gefunden habe, aber es ist ein sehr guter Trick. Verbringen Sie die Zeit damit, den Code zu verstehen, und es lohnt sich sehr.

Brad

0
Brad Phelan

Wenn Sie Facebook Folly verwenden, ist das Standardmakro hilfreich für Sie:

#include <folly/Traits.h>
namespace {
  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace

void some_func() {
  cout << "Does class Foo have a member int test() const? "
    << boolalpha << has_test_traits<Foo, int() const>::value;
}

Obwohl die Implementierungsdetails mit der vorherigen Antwort identisch sind, ist die Verwendung einer Bibliothek einfacher.

0