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.

37voto

Aaron McDaid Punkte 25409

Eine einfache Lösung für C++11:

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return "toString not defined";
}

Update, 3 Jahre später: (und dies ist ungetestet). Um zu testen, für die Existenz, ich denke, dies wird funktionieren:

template<class T>
constexpr auto test_has_toString_method(T* obj)
 -> decltype(  obj->toString() , std::true_type{} )
{
    return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
    return "toString not defined";
}

35voto

akim Punkte 7572

Nun, auf diese Frage gibt es bereits eine lange Liste von Antworten, aber ich möchte den Kommentar von Morwenn unterstreichen: es gibt einen Vorschlag für C++17, der es wirklich viel einfacher macht. Siehe N4502 für Details, aber als eigenständiges Beispiel betrachten Sie das Folgende.

Dieser Teil ist der konstante Teil, den Sie in eine Kopfzeile setzen.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

dann gibt es den variablen Teil, in dem Sie angeben, wonach Sie suchen (ein Typ, ein Mitgliedstyp, eine Funktion, eine Mitgliedsfunktion usw.). Im Fall der OP:

template <typename T>
using toString_t = decltype(std::declval<T>().toString());

template <typename T>
using has_toString = detect<T, toString_t>;

Das folgende Beispiel, entnommen aus N4502 zeigt eine aufwändigere Sonde:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

Im Vergleich zu den anderen oben beschriebenen Implementierungen ist diese recht einfach: ein reduzierter Satz von Werkzeugen ( void_t y detect ) reicht aus, keine Notwendigkeit für haarige Makros. Außerdem wurde berichtet (siehe N4502 ), dass er messbar effizienter ist (Kompilierzeit und Compiler-Speicherverbrauch) als frühere Ansätze.

Hier ist ein Live-Beispiel . Es funktioniert gut mit Clang, aber leider folgten GCC-Versionen vor 5.1 einer anderen Interpretation des C++11-Standards, was zu void_t nicht wie erwartet funktioniert. Yakk hat bereits für Abhilfe gesorgt: Verwenden Sie die folgende Definition von void_t ( void_t in der Parameterliste funktioniert, aber nicht als Rückgabetyp ):

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
  using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif

30voto

Konrad Rudolph Punkte 503837

Dafür sind die Typeneigenschaften da. Leider müssen sie manuell definiert werden. In Ihrem Fall stellen Sie sich Folgendes vor:

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
}

18voto

Dmytro Ovdiienko Punkte 600

Eine weitere Möglichkeit, dies in C++17 zu tun (inspiriert durch boost:hana ).

Wird einmal implementiert und kann viele Male verwendet werden. Es erfordert keine has_something<T> SFINAE-Typ-Eigenschaftsklassen.

Solución

////////////////////////////////////////////
// has_member implementation
////////////////////////////////////////////

#include <type_traits>

template<typename T, typename F>
constexpr auto has_member_impl(F&& f) -> decltype(f(std::declval<T>()), true)
{
  return true;
}

template<typename>
constexpr bool has_member_impl(...) { return false; }

#define has_member(T, EXPR) \
 has_member_impl<T>( [](auto&& obj)->decltype(obj.EXPR){} )

Test

////////////////////////////////////////////
// Test
////////////////////////////////////////////

#include <iostream>
#include <string>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from Example::toString()!"; }
};

struct Example2 {
    int X;
};

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

int main() {
    static_assert(has_member(Example, Foo), 
                  "Example class must have Foo member");
    static_assert(has_member(Example, Bar()), 
                  "Example class must have Bar() member function");
    static_assert(!has_member(Example, ZFoo), 
                  "Example class must not have ZFoo member.");
    static_assert(!has_member(Example, ZBar()), 
                  "Example class must not have ZBar() member function");

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

16voto

Hier ist der prägnanteste Weg, den ich in C++20 gefunden habe und der Ihrer Frage sehr nahe kommt:

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

Sehen Sie es live auf Godbolt: https://gcc.godbolt.org/z/5jb1d93Ms

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