Frage

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

Das Problem ähnelt dem hier genanntenhttp://www.gotw.ca/gotw/071.htmaber nicht dasselbe:Im Artikel von Sutters Buch antwortete er auf die Frage, dass eine Klasse C eine Mitgliedsfunktion mit einer bestimmten Signatur bereitstellen MUSS, sonst würde das Programm nicht kompiliert.Bei meinem Problem muss ich etwas tun, wenn eine Klasse diese Funktion hat, sonst „etwas anderes“.

Ein ähnliches Problem hatte boost::serialization, aber mir gefällt die von ihnen gewählte 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 Mitgliedsfunktion (in ihrem Fall „serialisieren“, die zwei Parameter eines bestimmten Typs akzeptiert) mit einer bestimmten Signatur, andernfalls Es kommt zu einem Kompilierungsfehler.Das bedeutet, sowohl eine aufdringliche als auch eine nicht aufdringliche 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 boost::serialization-Namespace überschreiben, damit Sie IN IHREM CLIENT-CODE den Namespace Boost und die Namespace-Serialisierung öffnen können!
  2. Der Stapel, um dieses Durcheinander zu lösen, betrug 10 bis 12 Funktionen.

Ich muss ein benutzerdefiniertes Verhalten für Klassen definieren, die nicht über diese Memberfunktion 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 Tipp geben, wie ich dieses Rätsel lösen kann?

War es hilfreich?

Lösung

Ich bin mir nicht sicher, ob ich Dich richtig verstehe, aber Sie können SFINAE ausnutzen Funktion Präsenz zur Compile-Zeit zu erfassen. Beispiel aus meinem Code (Tests, wenn Klasse Memberfunktion size_t used_memory () const).

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

Andere Tipps

Hier ist eine mögliche Implementierung auf C ++ 11-Funktionen zu verlassen. Es erkennt richtig die Funktion, auch wenn es geerbt hat (im Gegensatz zu der Lösung in der akzeptierten Antwort, wie Mike Kinghan beobachtet in seine Antwort ) .

Die Funktion dieser Schnipsel Tests für 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;
};

Verbrauch:

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

Die akzeptierte Antwort auf diese Frage von compiletime Mitglied-Funktion Selbstbeobachtung, obwohl es mit Recht beliebt ist, hat einen Haken, die beobachtet werden können, in dem folgenden Programm:

#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> tut nicht bieten int & T::operator*() const.

Wenn Sie auf diese Gotcha nicht schon klug sind, dann schauen Sie sich von der Definition der std::shared_ptr<T> im Header <memory> wird beleuchten. dass Implementierung wird std::shared_ptr<T> von einer Basisklasse abgeleitet ist von denen erbt es operator*() const. So die Vorlage Instanziierung SFINAE<U, &U::operator*>, die „Suche“ den Betreiber stellt für U = std::shared_ptr<T> wird nicht passieren, weil std::shared_ptr<T> nicht hat operator*() in seinem eigenen Recht und Template-Instantiierung nicht "Tun Vererbung".

Dieser Haken hat keinen Einfluss auf den bekannten SFINAE Ansatz: „Der sizeof () Trick“ verwendet wird, zum Erfassen, ob lediglich T einige Mitgliedsfunktion mf aufweist (siehe z.B. diese Antwort und Kommentare). Aber Feststellung, dass T::mf existiert oft (in der Regel?) nicht gut genug: Sie können herstellen müssen auch, dass es eine gewünschte Signatur hat. Das ist, wo die dargestellt Technik Scores. Die pointerized Variante der gewünschten Signatur wird in einem Parameter eines Vorlagentyp eingeschrieben, die von erfüllt werden müssen, &T::mf für die SFINAE Sonde erfolgreich zu sein. Aber diese Vorlage Instanziierung Technik gibt die falsche Antwort, wenn T::mf vererbt wird.

Eine sichere SFINAE Technik für compiletime Selbstbeobachtung von T::mf muss das vermeiden Verwendung von &T::mf innerhalb eines Template-Argument instanziiert eine Art, auf die SFINAE Funktionsvorlage Auflösung abhängt. Stattdessen Funktion SFINAE Vorlage Auflösung kann nur auf genau einschlägigen Typdeklarationen abhängen verwendet als Argument Typen der SFINAE Sonde Funktion überlastet.

Als eine Antwort auf die Frage, die ich werde von dieser Einschränkung bleibt veranschaulichen zum Nachweis von compiletime E T::operator*() const, für willkürlicher T und E. Das gleiche Muster gilt mutatis mutandis für jedes andere Mitglied Methodensignatur zu sondieren.

#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? */
};

Bei dieser Lösung wird die überladene SFINAE Sonde Funktion test() wird „aufgerufen . Rekursiv“(Natürlich ist es eigentlich gar nicht aufgerufen, sondern hat lediglich die Rückgabetypen von hypothetischen Anrufungen vom Compiler aufgelöst.)

Wir müssen für mindestens ein und höchstens zwei Punkte von Informationen sondieren:

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

Wir haben die Antworten von den Rückgabetyp eines einzelnen Anrufs Auswertung test(0,0). Das erledigt ist durch:

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

Dieser Aufruf kann auf die /* SFINAE operator-exists :) */ Überlastung aufgelöst werden von test(), oder es könnte auf die /* SFINAE game over :( */ Überlastung beheben. Es kann nicht auf die /* SFINAE operator-has-correct-sig :) */ Überlastung beheben, denn das ist ein nur ein Argument erwartet und wir zwei sind vorbei.

Warum passieren wir zwei? Einfach die Auflösung zu zwingen auszuschließen /* SFINAE operator-has-correct-sig :) */. Das zweite Argument hat keine andere signifance.

Dieser Aufruf von test(0,0) lösen wird nur /* SFINAE operator-exists :) */ falls der erste Argument 0 erfüllt hierbei der erste Parametertyp dieser Überlast, das ist decltype(&A::operator*) mit A = T. 0 wird diese Art erfüllen nur für den Fall T::operator* vorhanden ist.

Nehmen wir an, die Ja Compiler dazu sagen. Dann geht es mit /* SFINAE operator-exists :) */ und es muss den Rückgabetyp bestimmen, der Funktionsaufruf, der in diesem Fall decltype(test(&A::operator*)) ist - die retUrne Typ eines weiteren Aufruf test().

Dieses Mal, wir vorbei nur ein Argument, &A::operator*, was wir jetzt wissen existiert, oder wir würden nicht hier sein. Ein Aufruf von test(&A::operator*) könnte lösen entweder /* SFINAE operator-has-correct-sig :) */ oder wieder könnte /* SFINAE game over :( */ lösen. Der Anruf wird übereinstimmen /* SFINAE operator-has-correct-sig :) */ nur für den Fall &A::operator* erfüllt die einzelnen Parameter Typ dieser Überlast, die E (A::*)() const ist, mit A = T.

Der Compiler wird sagen Ja hier, wenn T::operator* diese Signatur gewünscht hat, und hat dann wieder den Rückgabetyp der Überlastung zu bewerten. Nicht mehr "Rekursion" jetzt. Es ist std::true_type

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

Hier ist ein Testprogramm, das die Vorlage zeigt die Herstellung des erwarteten in verschiedenen Beispielantworten der Fälle (GCC 4.6.3 wieder).

// 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 Fehler in dieser Idee? Kann es ohne noch einmal gemacht werden generic fallen Foul es der Haken vermeidet?

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

Nach Mitglied x in einer Klasse. Könnte sein, var, func, Klasse, Vereinigung oder Enum:

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

Nach Elementfunktion 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;

Nach Membervariable x:

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

Nach Mitglied Klasse x:

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

Nach Mitgliedsverband x:

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

Nach Mitglied Enum x:

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

Überprüfen Sie für jede Memberfunktion x unabhängig von 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;

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)

Dies sollte ausreichend sein, wenn Sie den Namen der Mitgliedsfunktion wissen Sie erwarten. (In diesem Fall schlägt die Funktion fehl bla zu instanziiert, wenn kein Funktionselement ist (eine zu schreiben, die sowieso funktioniert, ist hart, weil es einen Mangel an Funktion teilweise Spezialisierung ist. Sie müssen möglicherweise auch Klassen-Templates) verwenden, aktivieren Sie die Struktur (die ist ähnlich wie enable_if) auch von der Art der Funktion als Templat könnte man es als Mitglied haben will.

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

Hier ist ein einfaches Nehmen auf Mike Kinghan Antwort. Dies wird geerbten Methoden erkennen. Es wird auch für überprüfen Sie die genau Signatur (anders als Ansatz der JROK, die ermöglicht Konvertierungen Argument).

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

Beispiel

Sie können mit std :: is_member_function_pointer

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

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

Ich hatte selbst das gleiche Problem und fand die hier vorgeschlagenen Lösungen sehr interessant ...hatte aber den Bedarf an einer Lösung, die:

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

Habe noch einen gefunden Faden Ich schlage so etwas vor, basierend auf a BOOST-Diskussion.Hier ist die Verallgemeinerung der vorgeschlagenen Lösung als Zwei-Makro-Deklaration für die Merkmalsklasse 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 mit dem folgenden Prototyp zu einer Traits-Klasse 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)
}

Um dies zu erreichen werden wir verwenden müssen:

  1. Funktion Vorlage mit unterschiedlichen Rückgabetypen Überlastung je nachdem, ob die Methode ist verfügbar
  2. mit dem Meta-conditionals im type_traits Header Im Einklang wir ‚ll eine true_type oder false_type unserer Überlastungen zurückkehren möchten
  3. Deklarieren Sie die true_type Überlastung eine int und die false_type Überlastung erwartet Variadische Parameter erwarten zu nutzen:
  4. die Vorlage Spezifikation für die true_type Funktion Bei der Definition verwenden wir declval und decltype so dass wir die Funktion unabhängig von Rückgabetyp Unterschiede oder Überlastungen erkennen zwischen Methoden

Sie können ein Live-Beispiel für diese hier rel="nofollow. , aber ich werde erklären sie auch unter:

ich für die Existenz einer Funktion mit dem Namen test überprüfen möchten, die eine Art Cabrio von int nimmt, dann muss ich würde 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 ist true (Anmerkung besteht keine Notwendigkeit, eine spezielle Funktionalität zu schaffen, mit der void a::test() Überlastung zu bewältigen, die void a::test(int) akzeptiert wird)
  • decltype(hasTest<b>(0))::value ist true (Weil int ist konvertierbar double int b::test(double) ist, unabhängig vom Rückgabetyp akzeptiert)
  • decltype(hasTest<c>(0))::value ist false (c nicht über eine Methode namens test, die einen Typ Cabrio von int nimmt dafür nicht akzeptiert wird)

Diese Lösung hat zwei Nachteile:

  1. Erfordert eine pro Methodendeklaration eines Paares von Funktionen
  2. Erstellt Namespace Verschmutzung vor allem, wenn wir für ähnliche Namen testen wollen, zum Beispiel, was würden wir den Namen einer Funktion, die für eine test() Methode testen wollte?

So ist es wichtig, dass diese Funktionen in einem Detail Namensraum deklariert werden, oder im Idealfall, wenn sie nur mit einer Klasse verwendet werden, sollten sie privat von dieser Klasse deklariert werden. Zu diesem Zweck habe ich ein Makro geschrieben Sie abstrakt, diese Informationen zu helfen:

#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 nutzen könnten diese wie:

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

Anschließend ruft details::test_int<a>::value oder details::test_void<a>::value true oder false für die Zwecke der Inline-Code oder Meta-Programmierung ergeben würde.

Um nicht-intrusive, können Sie auch serialize im Namensraum der Klasse setzen serialisiert werden oder die Archivklasse, dank Koenig Lookup . Siehe Namensräume für Freie Funktionsüberschreibungen für mehr Details. : -)

Öffnen einer bestimmten Namensraum auf eine freie Funktion zu implementieren, ist einfach falsch. (Zum Beispiel sind Sie nicht öffnen Namespace std soll swap für eigene Typen zu implementieren, sondern sollte stattdessen Koenig-Lookup verwenden.)

Okay. Zweiter Versuch. Es ist in Ordnung, wenn du diese entweder nicht mögen, ich bin für mehr Ideen suchen.

Herb Sutter Artikel spricht über Züge. So können Sie eine Züge Klasse, deren Standard haben Instanziierung hat das Ausweichverhalten und für jede Klasse, wo Ihre Member-Funktion vorhanden ist, dann wird die Züge Klasse spezialisiert, um die Member-Funktion aufzurufen. Ich glaube, Herb Artikel eine Technik erwähnt, dies zu tun, so dass es nicht viel Kopieren und Einfügen einzubeziehen.

Wie gesagt, aber vielleicht wollen Sie nicht die zusätzliche Arbeit beteiligt mit „Tagging“ Klassen, die das Mitglied tun implementieren. In diesem Fall, ich suche bei einer dritten Lösung ....

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

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 clases in Frage, Aa sind die besondere, dass das Mitglied erbt, die wir suchen.

In der FooFinder die true_type und false_type ist der Ersatz für den entsprechenden C ++ 11 Klassen. Auch für das Verständnis der Vorlage Meta-Programmierung, offenbaren sie die Grundlage des SFINAE-sizeof-Tricks.

Die TypeSink ist eine Vorlage struct, die später verwendet wird, um das Integralergebnis des sizeof Bediener in eine Vorlage Instanziierung zu versenken, einen Typ zu bilden.

Die match Funktion ist eine weitere SFINAE Art von Vorlage, die ohne ein generisches Gegenstück verbleibt. Es kann daher nur instanziiert werden, wenn der Typ des Arguments den Typ passt es für spezialisierte.

Sowohl die test Funktionen zusammen mit der ENUM-Erklärung bilden schließlich die zentralen SFINAE Muster. Es gibt eine generischen ein Auslassungszeichen verwenden, die die false_type und ein Gegenstück mit spezifischeren Argumenten gibt Vorrang zu nehmen.

Um die test Funktion mit einem Template-Argumente von T zu instanziiert, muss die match Funktion instanziiert werden, da sein Rückgabetyp erforderlich ist, das TypeSink Argument zu instanziiert. Der Nachteil ist, dass &U::foo, in einem Funktionsargument gewickelt wird, ist nicht bezeichnet innerhalb einer Spezialisierung Template-Argument, so erbte Membersuche erfolgt nach wie vor.

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ülltes Beispiel hier

http://pastie.org/298994

Ich verwende die Technik, um die Anwesenheit eines Stütz Ostream Betreiber << auf der Klasse in Frage zu erkennen und dann ein anderes Stück Code erzeugt abhängig.

Ich glaubte nicht, ist es möglich, bevor die verknüpfte Lösung zu finden, aber es ist ein sehr netter Trick. Verbringen Sie die Zeit, um den Code zu verstehen, und es ist sehr die Mühe wert.

Brad

Wenn Sie Facebook Torheit verwenden, ihre sind aus der Box Makro, 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 Details der Implementierung ist das gleiche mit der vorherige Antwort, verwenden Sie eine Bibliothek ist einfacher.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top