2016-04-18 13 views
6

Ich weiß, dass es viele Möglichkeiten gibt zu erkennen, ob eine Klasse eine bestimmte Funktion hat, aber keine von ihnen wirklich für meinen genauen Fall funktioniert. Meine aktuelle Implementierung, um nach der korrekten Member-Funktion zu suchen, funktioniert mit Ausnahme von Vererbung-Funktionen.Typ Merkmal: Überprüfen Sie, ob die Klasse eine bestimmte Funktion hat (vielleicht erben)

#include <type_traits> 

template<typename T>                 
class HasFoo {                     
    template <typename U, int (U::*)(float)>         
     struct Check;                 

    template <typename U>              
     static std::true_type Test(Check<U, &U::foo> *);     

    template <typename U>               
     static std::false_type Test(...);            

public: 
    static constexpr bool value = decltype(Test<T>(0))::value;      
}; 

struct A { 
    int foo(float); 
}; 

struct B : public A { 
}; 

struct C { 
    unsigned int foo(double); 
}; 

struct D { 
    static int foo(float); 
}; 

static_assert(HasFoo<A>::value, "A should have foo."); 
static_assert(HasFoo<B>::value, "B should inherit foo from A."); 

static_assert(!HasFoo<C>::value, "C should not have foo."); 
static_assert(!HasFoo<D>::value, "Ds static foo should be false."); 

Live example.

Diese Implementierung nicht für die static_assert von B.

Eine inakzeptable Abhilfe zu überprüfen wäre funktioniert:

template <typename U, int (U::A::*)(float)> 
struct Check;     | 
           |- add base class 

Aber es hätte ich wissen die Basisklasse und das sollte vermieden werden.

Hat jemand eine Idee, wie man auch nach abgeleiteten Funktionen prüft?

Bearbeiten: Der Typ Merkmal sollte auch funktionieren, wenn überhaupt keine Foo vorhanden sind.

struct E {}; 
static_assert(!HasFoo<E>::value, "E does not have foo."); 

Antwort

3

ist hier eine alte Schule C++ 03 Art und Weise tun. In der Regel kann es als Dienstprogramm verwendet werden und es für jede Methode oder Variable geformt werden.

#define HasMember(NAME) \ 
    template<class Class, typename Type = void> \ 
    struct HasMember_##NAME \ 
    { \ 
    typedef char (&yes)[2]; \ 
    template<unsigned long> struct exists; \ 
    template<typename V> static yes Check (exists<sizeof(static_cast<Type>(&V::NAME))>*); \ 
    template<typename> static char Check (...); \ 
    static const bool value = (sizeof(Check<Class>(0)) == sizeof(yes)); \ 
    }; \ 
    template<class Class> \ 
    struct HasMember_##NAME<Class, void> \ 
    { \ 
    typedef char (&yes)[2]; \ 
    template<unsigned long> struct exists; \ 
    template<typename V> static yes Check (exists<sizeof(&V::NAME)>*); \ 
    template<typename> static char Check (...); \ 
    static const bool value = (sizeof(Check<Class>(0)) == sizeof(yes)); \ 
    } 

Instantiate:

HasMember(Foo); 

Nutzungs:

HasMember_Foo<B>::value // without type (but then no overload allowed) 
HasMember_Foo<C, int (C::*)(float)>::value // needs type 

Beachten Sie, dass, hier bin ich die Bereitstellung von zwei HasMember_Foo s, 1 mit Typ und 1 ohne Typ. Sie sind für jeden Typ verallgemeinert (nicht nur spezifisch für int (X::*)(float)). Wenn kein Typ angegeben ist, muss die Klasse nur eine solche Methode haben (ohne Überladung). Daher ist es immer sicherer, den Typ zu erwähnen; Wie Sie in Ihrer Frage getan haben, ist der spezifische Typ int (X::*)(float). BTW, dies kann auch mit einem anderen Makro enthalten sein.
Ohne ein solches zusätzliches Makro, im Fall von class C und class D, müssen Sie möglicherweise den Typ der Methode angeben.

Hier ist ein demo mit Ihrem Code.


Hier ist angenommen, dass je nachdem, welche Klasse Element (Funktion oder Variable) gewählt wird, muss public werden scoped. Wenn X::fooprivate ist, dann wird diese Lösung nicht funktionieren.

3

Hier ist ein Weg, es zu tun (Arbeit für Ihre 4 Testfälle, es ist nicht obwohl testete intensiv), dank @ Jarod42 für die Verbesserung (siehe erste Antwort am Ende):

template <typename T> 
int call_foo (int (T::*)(float)); 

template <typename C> 
std::true_type has_foo(decltype(call_foo(&C::foo))); 

template <typename C> 
std::false_type has_foo (...);  

template<typename T> 
using HasFoo = decltype(has_foo<T>(0)); 

Das Problem mit Ihrem Code war, dass Sie U::* erwartet, während &B::foo ist A::* (nicht B::*). Hier lasse ich den Compiler den Wert T mit implizitem Typ Abzug wählen, so dass ich nicht auf ein solches Problem stoße.

Der Code funktioniert wie folgt:

  • Wenn T kein foo Mitglied hat, dann wird der Compiler die zweite Überlastung von has_foo wählen.
  • Wenn T hat ein foo Mitglied, wird der Compiler die erste Überlastung versuchen aber fehl, da es keine passende call_foo Funktion ist so wird es wieder die zweite wählen und eine std::false_type machen.

Arbeitscode auf ideone: http://ideone.com/erh93I.

Sie können alles in einem class setzen, wenn Sie wollen:

template <typename T> 
class HasFoo { 

    template <typename C> 
    static int call_foo (int (C::*)(float)); 

    template <typename C> 
    static std::true_type has_foo (decltype(call_foo(&C::foo))); 

    template <typename C> 
    static std::false_type has_foo (...); 

public: 
    static constexpr bool value = decltype(has_foo<T>(0)){}; 
}; 
+0

Vielen Dank für die Antwort. Ich habe vergessen zu erwähnen: Wenn die Klasse überhaupt kein Foo hat, sollte sie kompilieren. Bearbeite meine Frage. – Viatorus

+0

@Viatorus Ich habe meine Antwort aktualisiert - Dies ist nicht so sauber wie die erste Version, aber das könnte verbessert werden. – Holt

Verwandte Themen