629 Stimmen

Vorgefertigte Prüfung auf das Vorhandensein einer Klassenmitgliedfunktion?

Ist es möglich, eine Vorlage zu schreiben, die das Verhalten ändert, je nachdem, ob eine bestimmte Member-Funktion auf eine Klasse definiert ist?

Hier ist ein einfaches Beispiel dafür, was ich schreiben würde:

template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return "toString not defined";
}

Also, wenn class T hat toString() definiert ist, dann wird sie verwendet, ansonsten nicht. Der magische Teil, von dem ich nicht weiß, wie er funktioniert, ist der "FUNCTION_EXISTS"-Teil.

4voto

anton_rh Punkte 6967

Die generische Vorlage, mit der überprüft werden kann, ob ein bestimmtes "Merkmal" vom Typ unterstützt wird:

#include <type_traits>

template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
    // these structs are used to recognize which version
    // of the two functions was chosen during overload resolution
    struct supported {};
    struct not_supported {};

    // this overload of chk will be ignored by SFINAE principle
    // if TypeChecker<Type_> is invalid type
    template <typename Type_>
    static supported chk(typename std::decay<TypeChecker<Type_>>::type *);

    // ellipsis has the lowest conversion rank, so this overload will be
    // chosen during overload resolution only if the template overload above is ignored
    template <typename Type_>
    static not_supported chk(...);

    // if the template overload of chk is chosen during
    // overload resolution then the feature is supported
    // if the ellipses overload is chosen the the feature is not supported
    static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};

Die Vorlage, die prüft, ob es eine Methode gibt foo die mit der Signatur kompatibel ist double(const char*)

// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));

Beispiele

// types that support has_foo
struct struct1 { double foo(const char*); };            // exact signature match
struct struct2 { int    foo(const std::string &str); }; // compatible signature
struct struct3 { float  foo(...); };                    // compatible ellipsis signature
struct struct4 { template <typename T>
                 int    foo(T t); };                    // compatible template signature

// types that do not support has_foo
struct struct5 { void        foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double      foo(      int *); }; // const char* can't be converted to int*
struct struct8 { double      bar(const char*); }; // there is no foo method

int main()
{
    std::cout << std::boolalpha;

    std::cout << is_supported<has_foo, int    >::value << std::endl; // false
    std::cout << is_supported<has_foo, double >::value << std::endl; // false

    std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct4>::value << std::endl; // true

    std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct8>::value << std::endl; // false

    return 0;
}

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4

4voto

user1095108 Punkte 13219

Wie wäre es mit dieser Lösung?

#include <type_traits>

template <typename U, typename = void> struct hasToString : std::false_type { };

template <typename U>
struct hasToString<U,
  typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };

4voto

Alexandre C. Punkte 53706

Seltsam, dass niemand den folgenden netten Trick vorgeschlagen hat, den ich einmal auf dieser Website gesehen habe:

template <class T>
struct has_foo
{
    struct S { void foo(...); };
    struct derived : S, T {};

    template <typename V, V> struct W {};

    template <typename X>
    char (&test(W<void (X::*)(), &X::foo> *))[1];

    template <typename>
    char (&test(...))[2];

    static const bool value = sizeof(test<derived>(0)) == 1;
};

Sie müssen sicherstellen, dass T eine Klasse ist. Es scheint, dass die Mehrdeutigkeit bei der Suche nach foo ein Substitutionsfehler ist. Ich habe es auf gcc arbeiten, nicht sicher, ob es Standard ist aber.

3voto

Sean Punkte 343

Ich denke, dass es möglich ist, universell festzustellen, ob etwas aufrufbar ist, ohne dass man für jedes einzelne Element ausführliche Type Traits erstellen oder experimentelle Funktionen oder langen Code verwenden muss:

template<typename Callable, typename... Args, typename = decltype(declval<Callable>()(declval<Args>()...))>
std::true_type isCallableImpl(Callable, Args...) { return {}; }

std::false_type isCallableImpl(...) { return {}; }

template<typename... Args, typename Callable>
constexpr bool isCallable(Callable callable) {
    return decltype(isCallableImpl(callable, declval<Args>()...)){};
}

Verwendung:

constexpr auto TO_STRING_TEST = [](auto in) -> decltype(in.toString()) { return {}; };
constexpr bool TO_STRING_WORKS = isCallable<T>(TO_STRING_TEST);

2voto

user3296587 Punkte 91

Es gibt hier viele Antworten, aber ich habe keine Version gefunden, die funktioniert real Methodenauflösungsreihenfolge, wobei keine der neueren C++-Funktionen verwendet wird (nur C++98-Funktionen).
Hinweis: Diese Version ist getestet und funktioniert mit vc++2013, g++ 5.2.0 und dem onlline Compiler.

Also habe ich mir eine Version ausgedacht, die nur sizeof() verwendet:

template<typename T> T declval(void);

struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);

struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};

template<typename T>
struct has_awesome_member {
 template<typename U> static yes_no<(sizeof((
   declval<U>().awesome_member(),fake_void()
  ))!=0)> check(int);
 template<typename> static no check(...);
 enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};

struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };

static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

Live-Demo (mit erweiterter Rückgabetypenprüfung und vc++2010-Workaround): http://cpp.sh/5b2vs

Keine Quelle, da ich selbst darauf gekommen bin.

Wenn Sie die Live-Demo auf dem g++-Compiler ausführen, beachten Sie bitte, dass Array-Größen von 0 erlaubt sind, was bedeutet, dass das verwendete static_assert keinen Compiler-Fehler auslöst, selbst wenn es fehlschlägt.
Eine häufig verwendete Umgehungslösung besteht darin, das "typedef" im Makro durch "extern" zu ersetzen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X