Überprüfen Sie, ob eine Klasse eine Mitgliedsfunktion einer bestimmten Signatur hat


135

Ich frage nach einem Vorlagentrick, um festzustellen, ob eine Klasse eine bestimmte Elementfunktion einer bestimmten Signatur hat.

Das Problem ähnelt dem hier genannten Problem http://www.gotw.ca/gotw/071.htm, ist jedoch nicht dasselbe: In Sutters Buch beantwortete er die Frage, dass eine Klasse C eine Mitgliedsfunktion bereitstellen MUSS eine bestimmte Signatur, 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 trat bei boost :: serialization auf, aber ich mag die Lösung nicht: eine Vorlagenfunktion, die standardmäßig eine freie Funktion (die Sie definieren müssen) mit einer bestimmten Signatur aufruft, es sei denn, Sie definieren eine bestimmte Elementfunktion ( in ihrem Fall "serialize", das 2 Parameter eines bestimmten Typs mit einer bestimmten Signatur verwendet, tritt andernfalls ein Kompilierungsfehler auf. Dies bedeutet, sowohl eine aufdringliche als auch eine nicht aufdringliche Serialisierung zu implementieren.

Ich mag diese Lösung aus zwei Gründen nicht:

  1. Um nicht aufdringlich zu sein, müssen Sie die globale "Serialize" -Funktion im Boost :: Serialization-Namespace überschreiben, damit Sie in Ihrem Kundencode den Namespace Boost und die Namespace-Serialisierung öffnen können!
  2. Der Stapel, um dieses Durcheinander zu beheben, 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?



@ R.MartinhoFernandes Was für eine Antwort suchst du? Diese Antwort von Mike Kinghan geht ziemlich tief und verwendet C ++ 11-Zeug.
Jrok

@ R.MartinhoFernandes Vielleicht ist dies die moderne Version, die Sie suchen?
Daniel Frey

Antworten:


90

Ich bin nicht sicher, ob ich Sie richtig verstehe, aber Sie können SFINAE nutzen, um das Vorhandensein von Funktionen zur Kompilierungszeit zu erkennen. Beispiel aus meinem Code (testet, ob die Klasse die Mitgliedsfunktion 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>());
}

14
Was zur Hölle ist das??? ist es legal C ++ Code? Kannst du "template <typname U, size_t (U :: *) () const>" schreiben? aber ... es ist eine großartige und neue Lösung! Ich danke Ihnen, ich werde morgen mit meinen Kollegen besser analysieren ... großartig!
ugasoft

2
Im Beispiel fehlt die Definition von 'int_to_type'. Natürlich trägt es nicht zur Antwort bei, aber es bedeutet, dass die Leute Ihren Code nach einem schnellen Ausschneiden und Einfügen in Aktion sehen können.
Richard Corden

2
Eine einfache Definition von int_to_type könnte sein: 'template <int N> struct int_to_type {};'. Viele Implementierungen halten den Parameter-N-Wert entweder in einer Aufzählung oder in einer statischen Ganzzahlkonstante (Vorlage <intN> struct int_to_type {enum {value = N};}; / template <intN> struct int_to_type {static const int value = N;})
David Rodríguez - Dribeas

2
Nehmen Sie einfach boost :: Integral_constant anstelle von int_to_type.
Vadim Ferderer

2
@JohanLundberg Es ist eine Zeiger-auf-(nicht-statische-) Mitgliedsfunktion. Zum Beispiel size_t(std::vector::*p)() = &std::vector::size;.
Stellen Sie Monica

132

Hier ist eine mögliche Implementierung, die auf C ++ 11-Funktionen basiert. 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 feststellt ).

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

Funktioniert dies, wenn Y keine Methode namens "serialize" hat? Ich sehe nicht, wie es einen falschen Wert zurückgeben würde, wenn die Methode "serialize" nicht existieren würde.
Collin

1
@Collin In diesem Fall schlägt das Ersetzen des Vorlagenparameters bei der ersten Überladung der Prüfung fehl und wird aus dem Überlastungssatz verworfen. Es wird auf den zweiten zurückgegriffen, der false_type zurückgibt. Dies ist kein Compilerfehler aufgrund des SFINAE-Prinzips.
Jrok

1
@ elios264 gibt es nicht. Sie können ein Makro verwenden, um eine Vorlage für jede Funktion zu schreiben, nach der Sie suchen möchten.
Jrok

1
Gibt es einen bestimmten Grund, warum das Argument für die Prüfung vom Typ T * und nicht vom Typ T oder T & ist?
Shibumi

1
Aber was ist, wenn der serializeselbst eine Vorlage akzeptiert? Gibt es eine Möglichkeit, die serializeExistenz zu testen, ohne den genauen Typ einzugeben?
Hi-Angel

37

Die akzeptierte Antwort auf diese Frage der Introspektion von Compiletime-Member-Funktionen ist zwar zu Recht beliebt, weist jedoch einen Haken auf, 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;
}

Errichtet mit GCC 4.6.3, die Programmausgaben 110- informiert uns , dass T = std::shared_ptr<int>sich nicht sorgen int & T::operator*() const.

Wenn Sie mit diesem Fall noch nicht vertraut sind, wird ein Blick auf die Definition von std::shared_ptr<T>in der Kopfzeile <memory>Licht ins Dunkel bringen. Wird in dieser Implementierung std::shared_ptr<T>von einer Basisklasse abgeleitet, von der es erbt operator*() const. Die Vorlageninstanziierung SFINAE<U, &U::operator*>, die das "Finden" des Operators für darstellt, U = std::shared_ptr<T>wird also nicht stattfinden, da std::shared_ptr<T>sie kein operator*()eigenes hat und die Vorlageninstanziierung keine "Vererbung" durchführt.

Dieser Haken wirkt sich nicht auf den bekannten SFINAE-Ansatz aus, bei dem "The sizeof () Trick" verwendet wird, um lediglich festzustellen, ob Teine Elementfunktion vorliegt mf(siehe z. B. diese Antwort und Kommentare). Aber festzustellen, dass es T::mfexistiert, ist oft (normalerweise?) Nicht gut genug: Möglicherweise müssen Sie auch feststellen, dass es eine gewünschte Signatur hat. Hier punktet die illustrierte Technik. Die mit Zeigern versehene Variante der gewünschten Signatur wird in einen Parameter eines Vorlagentyps eingeschrieben, der erfüllt sein muss, &T::mfdamit die SFINAE-Sonde erfolgreich ist. Diese Technik zur Instanziierung von Vorlagen gibt jedoch die falsche Antwort, wenn sie T::mfvererbt wird.

Eine sichere SFINAE-Technik für die Introspektion während der Kompilierung T::mfmuss die Verwendung &T::mfeines Template-Arguments vermeiden , 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, werde ich zur Erkennung der Kompilierungszeit E T::operator*() const, für willkürliche Tund E. Das gleiche Muster wird entsprechend angewendet , um nach Signaturen anderer Mitgliedsmethoden 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-Sondenfunktion test()"rekursiv aufgerufen". (Natürlich wird es überhaupt nicht aufgerufen; es enthält lediglich die vom Compiler aufgelösten Rückgabetypen hypothetischer Aufrufe.)

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

  • Existiert T::operator*()überhaupt? Wenn nicht, sind wir fertig.
  • Ist T::operator*()seine Unterschrift gegeben E T::operator*() const?

Wir erhalten die Antworten, indem wir den Rückgabetyp eines einzelnen Anrufs an bewerten test(0,0). Das wird gemacht von:

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

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

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

Dieser Aufruf von test(0,0)wird für den /* SFINAE operator-exists :) */Fall aufgelöst, dass das erste Argument 0 den ersten Parametertyp dieser Überladung erfüllt, dh decltype(&A::operator*)mit A = T. 0 wird diesen Typ nur für den Fall erfüllen, dass er T::operator*existiert.

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

Dieses Mal übergeben wir nur ein Argument, von &A::operator*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 aufgelöst werden /* SFINAE game over :( */. Der Aufruf stimmt /* SFINAE operator-has-correct-sig :) */nur für den Fall überein, &A::operator*dass der einzelne Parametertyp dieser Überlastung erfüllt ist, dh E (A::*)() constmit A = T.

Der Compiler sagt hier Ja, wenn er 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)oder nicht /* SFINAE operator-has-correct-sig :) */ für den Aufruf wählt test(&A::operator*), ist dies in beiden Fällen der Fall /* 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 (erneut 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 generischer gemacht werden, ohne erneut den Haken zu verlieren, den es vermeidet?


16

Hier sind einige Verwendungsausschnitte: * Der Mut für all dies ist weiter unten

Suchen Sie nach Mitgliedern xin einer bestimmten 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 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 prüfen x:

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

Nach Mitgliederklasse suchen 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 Mitgliederliste prüfen x:

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

Überprüfen Sie xunabhängig von der Signatur, ob ein Mitglied funktioniert :

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;

ODER

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)

1
Das ist toll; Es wäre schön, dies in eine einzelne Header-Dateibibliothek zu stellen.
Allan

12

Dies sollte ausreichen, wenn Sie den Namen der erwarteten Mitgliedsfunktion kennen. (In diesem Fall kann die Funktion bla nicht instanziiert werden, wenn keine Elementfunktion vorhanden ist (das Schreiben einer Funktion, die ohnehin funktioniert, ist schwierig, da es an einer teilweisen Spezialisierung der Funktionen mangelt. Möglicherweise müssen Sie Klassenvorlagen verwenden.) Auch die Aktivierungsstruktur (welche ähnelt enable_if) könnte auch als Vorlage für den Funktionstyp dienen, den 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);
}

4
Danke! Es ähnelt der von yrp vorgeschlagenen Lösung. Ich wusste nicht, dass Vorlagen über Elementfunktionen erstellt werden können. Das ist eine neue Funktion, die ich heute gelernt habe! ... und eine neue Lektion: "Sag niemals, dass du ein Experte für C ++ bist" :)
ugasoft

7

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

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, "");

Ausführbares Beispiel


Das ist gut, aber es wird nicht funktionieren, wenn die Funktion kein Argument
akzeptiert

Es funktioniert großartig. Ich hatte keine Probleme, diesen Trick auf Mitgliedsfunktionen anzuwenden, die keine Argumente enthielten.
JohnB

Dies funktioniert gut für mich mit mehreren und keinen Methodenargumenten, einschließlich Überladungen und einschließlich Vererbung, und mit der Verwendung von using, um Überladungen aus der Basisklasse zu bringen. Es funktioniert für mich auf MSVC 2015 und mit Clang-CL. Es funktioniert jedoch nicht mit MSVC 2012.
Steveire

5

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;

16
Wird nicht &A::fooein Compiler - Fehler, wenn es keine ist fooüberhaupt in A? Ich habe die ursprüngliche Frage so gelesen, dass sie mit jeder Eingabeklasse funktionieren soll, nicht nur mit solchen, bei denen ein Mitglied benannt ist foo.
Jeff Walden

5

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

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

Ich habe einen anderen Thread gefunden , der so etwas vorschlägt, basierend auf einer BOOST-Diskussion . Hier ist die Verallgemeinerung der vorgeschlagenen Lösung als Deklaration von zwei Makros für die Merkmalsklasse nach dem Modell der Klassen boost :: has_ ​​* .

#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 Merkmalsklasse 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 also 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)
}

5

Um dies zu erreichen, müssen wir verwenden:

  1. Überladen von Funktionsvorlagen mit unterschiedlichen Rückgabetypen, je nachdem, ob die Methode verfügbar ist
  2. In Übereinstimmung mit den Meta-Bedingungen im type_traitsHeader möchten wir ein true_typeoderfalse_type von unseren Überladungen zurückgeben
  3. Deklarieren Sie die true_typeÜberlastung, die einen erwartet, intund die false_typeÜberlast, die Variadic-Parameter ausnutzt: "Die niedrigste Priorität der Ellipsenkonvertierung bei der Überlastauflösung"
  4. Bei der Definition der Vorlage Spezifikation für die true_typeFunktion verwenden wir declvalund decltypees uns ermöglicht , die Funktion unabhängig von Rückgabetyp Unterschieden oder Überlastungen zwischen Methoden zu erfassen ,

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

Ich möchte prüfen, ob eine Funktion mit dem Namen vorhanden ist, von testder ein konvertierbarer Typ stammt int. Dann muss 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))::valueis true(Beachten Sie, dass keine speziellen Funktionen erstellt werden müssen, um mit der void a::test()Überlastung umzugehen. Dies void a::test(int)wird akzeptiert.)
  • decltype(hasTest<b>(0))::valueis true(Weil intkonvertierbar in double int b::test(double)akzeptiert wird, unabhängig vom Rückgabetyp)
  • decltype(hasTest<c>(0))::valueis false( chat keine benannte Methode test, die einen von dort konvertierbaren Typ akzeptiert, intdaher wird dies nicht akzeptiert)

Diese Lösung hat zwei Nachteile:

  1. Erfordert eine Deklaration pro Methode für ein Funktionspaar
  2. Erzeugt eine Verschmutzung des Namespaces, insbesondere wenn wir nach ähnlichen Namen testen möchten. Wie würden wir beispielsweise eine Funktion benennen, die nach einer test()Methode testen möchte ?

Daher ist es wichtig, dass diese Funktionen in einem Detail-Namespace deklariert werden. Wenn sie nur mit einer Klasse verwendet werden sollen, sollten sie von dieser Klasse privat deklariert werden. Zu diesem Zweck habe ich ein Makro geschrieben, mit dem Sie diese Informationen abstrahieren 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)
}

Anschließend aufrufen details::test_int<a>::valueoder details::test_void<a>::valueergeben trueoder falsezum Zwecke des Inline-Codes oder der Metaprogrammierung.


3

Um nicht aufdringlich zu sein, können Sie serializedank Koenig Lookup auch den Namespace der zu serialisierenden Klasse oder der Archivklasse eingeben . Weitere Informationen finden Sie unter Namespaces für freie Funktionsüberschreibungen . :-)

Das Öffnen eines bestimmten Namespace zum Implementieren einer freien Funktion ist einfach falsch. (z. B. sollten Sie keinen Namespace öffnen std, um ihn swapfür Ihre eigenen Typen zu implementieren , sondern stattdessen die Koenig-Suche verwenden.)


2

In Ordnung. Zweiter Versuch. Es ist in Ordnung, wenn Sie dieses auch nicht mögen, ich suche nach mehr Ideen.

Herb Sutters Artikel spricht über Eigenschaften. Sie können also eine Merkmalsklasse haben, deren Standardinstanziierung das Fallback-Verhalten aufweist. Für jede Klasse, in der Ihre Mitgliedsfunktion vorhanden ist, ist die Merkmalsklasse darauf spezialisiert, die Mitgliedsfunktion aufzurufen. Ich glaube, Herbs Artikel erwähnt eine Technik, um dies zu tun, damit nicht viel kopiert und eingefügt wird.

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


eh ... Ich habe diese Lösung analysiert ... Ich denke, sie ist für Benutzer meines Frameworks etwas zu teuer. (ok, ich gebe zu, ich entwickle ein Streaming-Framework und wähle zwischen der Erweiterung von iostream oder dem Umschreiben von etwas
Einfacherem

Meine dritte Lösung wäre die Verwendung von SFINAE. Da die Antwort von yrp es bereits erwähnt, werde ich nicht darauf eingehen (weil ich noch darüber recherchiere: Ich kenne die Idee, aber der Teufel steckt im Detail), es sei denn, seine Lösung funktioniert am Ende nicht für Sie . :-)
Chris Jester-Young

2

Sie scheinen die Detektorsprache zu wollen. Die obigen Antworten sind Variationen davon, die mit C ++ 11 oder C ++ 14 funktionieren.

Die std::experimentalBibliothek verfügt über Funktionen, die dies im Wesentlichen tun. Wenn Sie ein Beispiel von oben überarbeiten, könnte es sein:

#include <experimental/type_traits>

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;

Wenn Sie std :: experimental nicht verwenden können, kann eine rudimentäre Version wie folgt erstellt werden:

template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;

Da has_serialize_t tatsächlich entweder std :: true_type oder std :: false_type ist, kann es über eine der gängigen SFINAE-Redewendungen verwendet werden:

template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}

Oder durch Verwendung von Versand mit Überlastauflösung:

template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
  // call serialize here.
}

template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
  // do something else here.
}

template<class T>
std::string Serialize(const T& t) {
  return SerializeImpl(has_serialize_t<T>{}, t);
}

1

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, AaUnd Bsind die clases in Frage, Aadie besonderen sein , dass erbt das Element wir suchen.

In der FooFinderdie true_typeund false_typeist der Ersatz für die entsprechenden C ++ 11 Klassen. Auch für das Verständnis der Template-Meta-Programmierung enthüllen sie die Grundlage des SFINAE-Trickgrößen.

Das TypeSinkist eine Vorlage struct , die später verwendet wird , um das Integralergebnis des versenken sizeofBediener in eine Vorlage Instanziierung einen Typ zu bilden.

Die matchFunktion ist eine andere SFINAE-Vorlage, die ohne generisches Gegenstück verbleibt. Es kann daher nur instanziiert werden, wenn der Typ seines Arguments mit dem Typ übereinstimmt, auf den es spezialisiert wurde.

Beide testFunktionen bilden zusammen mit der Enum-Deklaration schließlich das zentrale SFINAE-Muster. Es gibt eine generische, die eine Ellipse verwendet, die das false_typeund ein Gegenstück mit spezifischeren Argumenten zurückgibt , um Vorrang zu haben.

Um die testFunktion mit einem Vorlagenargument von instanziieren zu können, muss Tdie matchFunktion instanziiert werden, da ihr Rückgabetyp erforderlich ist, um das TypeSinkArgument zu instanziieren . Die Einschränkung besteht darin &U::foo, dass das Einschließen in ein Funktionsargument nicht innerhalb einer Vorlagenargumentspezialisierung referenziert wird, sodass die Suche nach geerbten Mitgliedern weiterhin stattfindet.


1

Wenn Sie Facebook-Torheit verwenden, sind diese sofort einsatzbereit, um Ihnen zu helfen:

#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

Ich hatte ein ähnliches Bedürfnis und stieß auf dieses SO. Hier werden viele interessante / leistungsstarke Lösungen vorgeschlagen, die jedoch nur für einen bestimmten Bedarf etwas lang sind: Ermitteln Sie, ob eine Klasse eine Mitgliedsfunktion mit einer präzisen Signatur hat. Also habe ich etwas gelesen / getestet und mir meine Version ausgedacht, die von Interesse sein könnte. Es erkennt:

  • statische Elementfunktion
  • nicht statische Elementfunktion
  • nicht statische Elementfunktion const

mit einer genauen Unterschrift. Da ich keine Signatur erfassen muss (was eine kompliziertere Lösung erfordern würde), passt diese zu mir. Grundsätzlich wurde enable_if_t verwendet .

struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };

// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
                        std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T> 
                      > : std::true_type {};

template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
                  std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
                > : std::true_type {};

template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <typename T,
                        std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
                      > : std::true_type {};

int main ()
{
    constexpr bool has_sum_val = has_static_sum<Foo>::value;
    constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;

    constexpr bool has_calc_val = has_calc<Bar>::value;
    constexpr bool not_has_calc_val = !has_calc<Foo>::value;

    constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
    constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;

    std::cout<< "           has_sum_val " << has_sum_val            << std::endl
             << "       not_has_sum_val " << not_has_sum_val        << std::endl
             << "          has_calc_val " << has_calc_val           << std::endl
             << "      not_has_calc_val " << not_has_calc_val       << std::endl
             << "    has_calc_const_val " << has_calc_const_val     << std::endl
             << "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}

Ausgabe :

           has_sum_val 1
       not_has_sum_val 1
          has_calc_val 1
      not_has_calc_val 1
    has_calc_const_val 1
not_has_calc_const_val 1

0

Aufbauend auf der Antwort von jrok habe ich die Verwendung verschachtelter Vorlagenklassen und / oder -funktionen vermieden.

#include <type_traits>

#define CHECK_NESTED_FUNC(fName) \
    template <typename, typename, typename = std::void_t<>> \
    struct _has_##fName \
    : public std::false_type {}; \
    \
    template <typename Class, typename Ret, typename... Args> \
    struct _has_##fName<Class, Ret(Args...), \
        std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
    : public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
    {}; \
    \
    template <typename Class, typename Signature> \
    using has_##fName = _has_##fName<Class, Signature>;

#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value

Wir können die obigen Makros wie folgt verwenden:

class Foo
{
public:
    void Bar(int, const char *) {}
};

CHECK_NESTED_FUNC(Bar);  // generate required metafunctions

int main()
{
    using namespace std;
    cout << boolalpha
         << HAS_NESTED_FUNC(Foo, Bar, void(int, const char *))  // prints true
         << endl;
    return 0;
}

Vorschläge sind willkommen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.