Template-Check für die Existenz einer Klassenmitgliedsfunktion?

Lesezeit: 13 Minuten

Template Check fur die Existenz einer Klassenmitgliedsfunktion
Andy

Ist es möglich, eine Vorlage zu schreiben, die das Verhalten ändert, je nachdem, ob eine bestimmte Elementfunktion für eine Klasse definiert ist?

Hier ist ein einfaches Beispiel dafür, was ich schreiben möchte:

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, dann verwendet es es; ansonsten nicht. Der magische Teil, den ich nicht kenne, ist der Teil “FUNCTION_EXISTS”.

  • Natürlich versteht es sich von selbst, dass die unten stehende(n) Vorlagenantwort(en) nur mit Informationen zur Kompilierzeit funktionieren, dh T muss toString haben. Wenn Sie eine Unterklasse von T that übergeben tut definieren toString, aber T tut es nichtwird Ihnen gesagt, dass toString nicht definiert ist.

    – Alice Purcell

    2. September 2010 um 9:50 Uhr

  • Mögliches Duplikat Wie kann überprüft werden, ob ein Elementname (Variable oder Funktion) in einer Klasse vorhanden ist, mit oder ohne Angabe des Typs?, da es ein breiteres Problem mit C++03 bis C++1y abdeckt.

    – iammilind

    18. März 2016 um 9:13 Uhr

  • Beachten Sie, dass C++20 dies jetzt mit Konzepten zulässt. Siehe stackoverflow.com/questions/58394556/… und en.cppreference.com/w/cpp/language/constraints

    – Etham

    2. Dezember 2021 um 21:07 Uhr

Template Check fur die Existenz einer Klassenmitgliedsfunktion
Xeo

Diese Frage ist alt, aber mit C ++ 11 haben wir eine neue Möglichkeit, auf das Vorhandensein einer Funktion (oder wirklich eines Nicht-Typ-Members) zu prüfen, indem wir uns wieder auf SFINAE verlassen:

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
  os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
  obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
  serialize_imp(os, obj, 0);
}

Nun zu einigen Erklärungen. Als erstes verwende ich Ausdruck SFINAE ausschließen serialize(_imp) Funktionen aus der Überladungsauflösung, wenn der erste Ausdruck drin ist decltype ist nicht gültig (auch bekannt als die Funktion existiert nicht).

Die void() wird verwendet, um den Rückgabetyp all dieser Funktionen zu erstellen void.

Die 0 Argument wird verwendet, um die zu bevorzugen os << obj überladen, wenn beide verfügbar sind (literal 0 ist vom Typ int und daher passt die erste Überladung besser).


Jetzt möchten Sie wahrscheinlich, dass ein Merkmal überprüft, ob eine Funktion vorhanden ist. Zum Glück ist es einfach, das zu schreiben. Beachten Sie jedoch, dass Sie ein Merkmal schreiben müssen du selbst für jeden anderen gewünschten Funktionsnamen.

#include <type_traits>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T, class A0>
  static auto test_stream(int)
      -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
  template<class, class A0>
  static auto test_stream(long) -> std::false_type;
} // detail::

template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

Live-Beispiel.

Und weiter zu Erklärungen. Zuerst, sfinae_true ist ein Helfertyp, und es läuft im Grunde auf dasselbe hinaus wie Schreiben decltype(void(std::declval<T>().stream(a0)), std::true_type{}). Der Vorteil ist einfach, dass es kürzer ist.
Als nächstes die struct has_stream : decltype(...) erbt von beiden std::true_type oder std::false_type am Ende, je nachdem, ob die decltype Einchecken test_stream scheitert oder nicht.
Zuletzt, std::declval gibt Ihnen einen “Wert” von welchem ​​Typ auch immer Sie übergeben, ohne dass Sie wissen müssen, wie Sie ihn konstruieren können. Beachten Sie, dass dies nur in einem nicht ausgewerteten Kontext möglich ist, z decltype, sizeof und andere.


Beachten Sie, dass decltype ist nicht unbedingt erforderlich, da sizeof (und alle nicht ausgewerteten Kontexte) haben diese Verbesserung erhalten. Es ist nur so dass decltype liefert schon einen Typ und ist als solcher einfach sauberer. Hier ist ein sizeof Version einer der Überladungen:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

Die int und long Parameter sind aus demselben Grund immer noch vorhanden. Der Array-Zeiger wird verwendet, um einen Kontext bereitzustellen, wo sizeof kann verwendet werden.

  • Der Vorteil von decltype über sizeof ist auch, dass ein Temporär nicht durch speziell gestaltete Regeln für Funktionsaufrufe eingeführt wird (Sie müssen also keine Zugriffsrechte auf den Destruktor des Rückgabetyps haben und verursachen keine implizite Instanziierung, wenn der Rückgabetyp eine Klassenvorlagen-Instanziierung ist ).

    – Johannes Schaub – litb

    11. April 2014 um 17:29 Uhr


  • Microsoft hat Expression SFINAE noch nicht in seinen C++-Compiler implementiert. Stellen Sie sich nur vor, ich könnte einigen Leuten helfen, Zeit zu sparen, da ich verwirrt war, warum dies bei mir nicht funktionierte. Schöne Lösung, ich kann es kaum erwarten, sie in Visual Studio zu verwenden!

    – Jonathan

    26. Januar 2015 um 6:22 Uhr

  • Das muss man sagen static_assert(has_stream<X, char>() == true, "fail X"); wird kompilieren und nicht behaupten, weil char in int konvertierbar ist. Wenn dieses Verhalten also nicht erwünscht ist und ich möchte, dass alle Argumenttypen übereinstimmen, weiß ich nicht, wie das erreicht werden kann?

    – Gabriel

    4. Oktober 2015 um 14:10 Uhr


  • Wenn Sie genauso verwirrt sind wie ich über die beiden Argumente für decltype: decltype akzeptiert wirklich nur eines; das Komma ist hier ein Operator. Siehe stackoverflow.com/questions/16044514/…

    – André

    17. Februar 2017 um 6:06 Uhr


  • Dies funktioniert perfekt in Situationen, die vollständige Typen erfordern, aber in Situationen, in denen dies nicht der Fall ist, führt dies zu falsch negativen Ergebnissen für unvollständige (vorwärts deklarierte) Typen. Ich habe ein hinzugefügt sfinae_false Gegenstück und verwendet einen Rückgabetyp auf der long Überschreiben Sie das, was für das Vorhandensein eines Destruktors erkannt wurde. Dies schloss Typen aus, die noch unvollständig waren oder keine öffentlichen Destruktoren hatten. Das Ausschließen von nicht-öffentlichen Destruktoren war für mich akzeptabel.

    – John

    22. Mai 2017 um 18:23 Uhr

  • Ich mag wie type_check wird verwendet, um sicherzustellen, dass die Signaturen genau übereinstimmen. Gibt es eine Möglichkeit, es so zu gestalten, dass es mit jeder Methode übereinstimmt, die auf die Weise aufgerufen werden könnte, dass eine Methode mit Signatur Sign könnte angerufen werden? (zB wenn Sign = std::string(T::*)()ermöglichen std::string T::toString(int default = 42, ...) passen.)

    – j_random_hacker

    17. November 2010 um 1:37 Uhr

  • Ich habe nur etwas darüber herausgefunden, das mir nicht sofort klar war, also falls es anderen hilft: chk ist nicht und muss nicht definiert werden! Der Operator sizeof bestimmt die Größe der Ausgabe von chk, ohne dass chk jemals aufgerufen werden muss.

    – SCFranzösisch

    23. Januar 2011 um 17:24 Uhr

  • @deek0146: Ja, T darf kein primitiver Typ sein, da die Pointer-to-method-of-T-Deklaration nicht SFINAE unterliegt und für alle Nicht-Klassen-T einen Fehler verursacht. IMO ist die einfachste Lösung, mit zu kombinieren is_class Check von Boost.

    – Jan Hudec

    22. Mai 2012 um 12:04 Uhr

  • Wie kann ich das zum Laufen bringen, wenn meine toString ist eine Template-Funktion?

    – Frank

    20. August 2012 um 2:00 Uhr


  • Ist das (oder etwas Äquivalentes) in Boost?

    – Dan Nissenbaum

    29. März 2013 um 6:24 Uhr

Template Check fur die Existenz einer Klassenmitgliedsfunktion
Morwenn

C++20 – requires Ausdrücke

Mit C++20 kommen Konzepte und verschiedene Tools wie z requires Ausdrücke Dies ist eine integrierte Methode, um das Vorhandensein einer Funktion zu überprüfen. Mit ihnen könntest du deine umschreiben optionalToString funktionieren wie folgt:

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

Pre-C++20 – Erkennungs-Toolkit

N4502 schlägt ein Erkennungs-Toolkit zur Aufnahme in die C++17-Standardbibliothek vor, das es schließlich in die Bibliotheksgrundlagen TS v2 geschafft hat. Es wird höchstwahrscheinlich nie in den Standard aufgenommen, weil es von subsumiert wurde requires Ausdrücke da, aber es löst das Problem immer noch auf etwas elegante Weise. Das Toolkit führt einige Metafunktionen ein, darunter std::is_detected die verwendet werden kann, um einfach Typ- oder Funktionserkennungs-Metafunktionen darüber zu schreiben. So könnten Sie es verwenden:

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

template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

Beachten Sie, dass das obige Beispiel ungetestet ist. Das Erkennungs-Toolkit ist noch nicht in Standardbibliotheken verfügbar, aber der Vorschlag enthält eine vollständige Implementierung, die Sie einfach kopieren können, wenn Sie sie wirklich brauchen. Es spielt gut mit der C++17-Funktion if constexpr:

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

C++14 – Boost.Hana

Boost.Hana baut anscheinend auf diesem konkreten Beispiel auf und stellt in seiner Dokumentation eine Lösung für C++14 bereit, also werde ich es direkt zitieren:

[…] Hana bietet eine is_valid Funktion, die mit generischen C++14-Lambdas kombiniert werden kann, um eine viel sauberere Implementierung derselben Sache zu erhalten:

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

Damit haben wir ein Funktionsobjekt has_toString die zurückgibt, ob der angegebene Ausdruck für das übergebene Argument gültig ist. Das Ergebnis wird als zurückgegeben IntegralConstant, daher ist constexpr-ness hier kein Problem, da das Ergebnis der Funktion ohnehin als Typ dargestellt wird. Jetzt ist die Absicht nicht nur weniger ausführlich (das ist ein Einzeiler!), sondern auch viel klarer. Weitere Vorteile sind die Tatsache, dass has_toString kann an Algorithmen höherer Ordnung übergeben und auch im Funktionsbereich definiert werden, sodass der Namespace-Bereich nicht mit Implementierungsdetails verschmutzt werden muss.

Boost.TTI

Ein weiteres etwas idiomatisches Toolkit zur Durchführung einer solchen Prüfung – wenn auch weniger elegant – ist Boost.TTI, eingeführt in Boost 1.54.0. Für Ihr Beispiel müssten Sie das Makro verwenden BOOST_TTI_HAS_MEMBER_FUNCTION. So könnten Sie es verwenden:

#include <boost/tti/has_member_function.hpp>

// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)

// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

Dann könnten Sie die verwenden bool um einen SFINAE-Check zu erstellen.

Erläuterung

Das Makro BOOST_TTI_HAS_MEMBER_FUNCTION erzeugt die Metafunktion has_member_function_toString die den überprüften Typ als ersten Vorlagenparameter verwendet. Der zweite Vorlagenparameter entspricht dem Rückgabetyp der Memberfunktion, und die folgenden Parameter entsprechen den Typen der Parameter der Funktion. Das Mitglied value enthält true wenn die Klasse T hat eine Mitgliedsfunktion std::string toString().

Alternative, has_member_function_toString kann einen Member-Funktionszeiger als Vorlagenparameter annehmen. Daher ist ein Austausch möglich has_member_function_toString<T, std::string>::value durch has_member_function_toString<std::string T::* ()>::value.

  • kürzer als 03

    – ZFY

    13. März 2020 um 11:20 Uhr

  • @ZFY Ich denke, dass Boost.TTI auch mit C ++ 03 funktioniert, aber es ist die am wenigsten elegante Lösung von allen.

    – Morwenn

    13. März 2020 um 14:01 Uhr

  • Ist die C++20-Lösung wirklich valide? Ich hätte es gerne – aber es wird von g ++ und msvc abgelehnt – nur von clang akzeptiert.

    – Bernd

    4. April 2020 um 20:17 Uhr


  • Bei cpreference können Sie lesen: Wenn ein require-Ausdruck ungültige Typen oder Ausdrücke in seinen Anforderungen enthält und nicht in der Deklaration einer Entität mit Vorlagen erscheint, dann ist das Programm falsch formatiert.

    – Bernd

    4. April 2020 um 20:36 Uhr


  • @BerndBaumanns Wirklich? Ich habe es mit GCC-Trunk zum Laufen gebracht: godbolt.org/z/CBwZdE Vielleicht hast du Recht, ich habe nur geprüft, ob es funktioniert, aber nicht geprüft, ob es nach dem Standardwortlaut legal ist.

    – Morwenn

    5. April 2020 um 11:51 Uhr

Obwohl diese Frage zwei Jahre alt ist, wage ich es, meine Antwort hinzuzufügen. Hoffentlich wird es die vorherige, unbestreitbar hervorragende Lösung verdeutlichen. Ich habe die sehr hilfreichen Antworten von Nicola Bonelli und Johannes Schaub genommen und sie zu einer Lösung zusammengeführt, die meiner Meinung nach lesbarer, übersichtlicher ist und die nicht erfordert typeof Verlängerung:

template <class Type>
class TypeHasToString
{
    // This type won't compile if the second template parameter isn't of type T,
    // so I can put a function pointer type in the first parameter and the function
    // itself in the second thus checking that the function has a specific signature.
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    // A helper struct to hold the declaration of the function pointer.
    // Change it if the function signature changes.
    template <typename T> struct ToString
    {
        typedef void (T::*fptr)();
    };

    template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
    template <typename T> static No  HasToString(...);

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

Ich habe es mit gcc 4.1.2 überprüft. Der Verdienst geht hauptsächlich an Nicola Bonelli und Johannes Schaub, also geben Sie ihnen eine Stimme, wenn Ihnen meine Antwort hilft 🙂

  • kürzer als 03

    – ZFY

    13. März 2020 um 11:20 Uhr

  • @ZFY Ich denke, dass Boost.TTI auch mit C ++ 03 funktioniert, aber es ist die am wenigsten elegante Lösung von allen.

    – Morwenn

    13. März 2020 um 14:01 Uhr

  • Ist die C++20-Lösung wirklich valide? Ich hätte es gerne – aber es wird von g ++ und msvc abgelehnt – nur von clang akzeptiert.

    – Bernd

    4. April 2020 um 20:17 Uhr


  • Bei cpreference können Sie lesen: Wenn ein require-Ausdruck ungültige Typen oder Ausdrücke in seinen Anforderungen enthält und nicht in der Deklaration einer Entität mit Vorlagen erscheint, dann ist das Programm falsch formatiert.

    – Bernd

    4. April 2020 um 20:36 Uhr


  • @BerndBaumanns Wirklich? Ich habe es mit GCC-Trunk zum Laufen gebracht: godbolt.org/z/CBwZdE Vielleicht hast du Recht, ich habe nur geprüft, ob es funktioniert, aber nicht geprüft, ob es nach dem Standardwortlaut legal ist.

    – Morwenn

    5. April 2020 um 11:51 Uhr

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 das ist ungetestet). Um die Existenz zu testen, denke ich, dass dies funktionieren wird:

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

  • Das ist einfach und elegant, beantwortet aber streng genommen nicht die Frage von OP: Sie ermöglichen es dem Anrufer nicht prüfen für die Existenz einer Funktion, Sie immer bieten es. Aber trotzdem nett.

    – Adrian W

    13. Juni 2018 um 17:38 Uhr

  • @AdrianW, guter Punkt. Ich habe meine Antwort aktualisiert. Ich habe es aber nicht getestet

    – Aaron McDaid

    15. Juni 2018 um 18:49 Uhr

  • Falls es jemand anderem hilft, könnte ich das nicht ohne machen template<typename> vor der variadischen Überlastung: Es wurde nicht für eine Lösung in Betracht gezogen.

    – aPonza

    21. Dezember 2018 um 7:55 Uhr

  • Auch dies ist ungültiges C++11.

    – Petrus

    11. Oktober 2019 um 7:05 Uhr

1003260cookie-checkTemplate-Check für die Existenz einer Klassenmitgliedsfunktion?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy