Vorlagenprüfung für das Vorhandensein einer Klassenmitgliedsfunktion?


498

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

Wenn also class That toString()definiert, dann verwendet er es; sonst nicht. Der magische Teil, den ich nicht zu tun weiß, ist der Teil "FUNCTION_EXISTS".


6
Es versteht sich von selbst, dass die folgenden Vorlagenantworten nur mit Informationen zur Kompilierungszeit funktionieren, dh T muss toString haben. Wenn Sie in einer Unterklasse von T übergeben , die tut toString definieren, aber T tut nicht , wird dir gesagt werden toString ist nicht definiert.
Alice Purcell

Antworten:


319

Ja, mit SFINAE können Sie überprüfen, ob eine bestimmte Klasse eine bestimmte Methode bietet. Hier ist der Arbeitscode:

#include <iostream>

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test( typeof(&C::helloworld) ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

Ich habe es gerade mit Linux und gcc 4.1 / 4.3 getestet. Ich weiß nicht, ob es auf andere Plattformen portierbar ist, auf denen andere Compiler ausgeführt werden.


18
Obwohl ich für 'eins' und 'zwei' Folgendes verwendet habe: typedef char Small; Klasse Big {char dummy [2];}, um keine Unklarheiten über die plattformabhängige Variablengröße sicherzustellen.
user23167

6
Ich bezweifle, dass es auf der Erde eine Plattform mit der Größe (char) == sizeof (long) gibt
Nicola Bonelli

17
Ich bin mir nicht ganz sicher, aber ich denke nicht, dass dies tragbar ist. typeof ist eine GCC-Erweiterung, die auf anderen Compilern nicht funktioniert.
Leon Timmermans

56
typeof wird nicht benötigt - char [sizeof (& C :: helloworld)] funktioniert ebenfalls. Und um sizeof (long) == sizeof (char) zu vermeiden, verwenden Sie eine struct {char [2]};. Es muss eine Größe> = 2 haben
MSalters

57
Trivial, aber ich habe eine Weile gebraucht, um herauszufinden: Ersetzen typeofdurch, decltypewenn C ++ 0x verwendet wird , z. B. über -std = c ++ 0x.
Stunde

264

Diese Frage ist alt, aber mit C ++ 11 haben wir eine neue Möglichkeit, die Existenz einer Funktion (oder die Existenz eines Nicht-Typ-Mitglieds) zu überprüfen, wobei 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 den Ausdruck SFINAE , um die serialize(_imp)Funktionen von der Überladungsauflösung auszuschließen , wenn der erste Ausdruck darin decltypenicht gültig ist (auch bekannt als die Funktion existiert nicht).

Mit void()wird der Rückgabetyp aller dieser Funktionen festgelegt void.

Das 0Argument wird verwendet, um die os << objÜberladung zu bevorzugen, wenn beide verfügbar sind (Literal 0ist vom Typ intund als solches stimmt die erste Überladung besser überein).


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 für jeden gewünschten Funktionsnamen selbst ein Merkmal schreiben müssen .

#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 den Erklärungen. Erstens sfinae_truehandelt es sich um einen Hilfstyp, der im Grunde dem Schreiben entspricht decltype(void(std::declval<T>().stream(a0)), std::true_type{}). Der Vorteil ist einfach, dass es kürzer ist.
Als nächstes struct has_stream : decltype(...)erbt das entweder std::true_typeoder std::false_typeam Ende, je nachdem, ob das decltypeEinchecken test_streamfehlschlägt oder nicht.
Zuletzt erhalten std::declvalSie einen "Wert" für jeden Typ, den Sie übergeben, ohne dass Sie wissen müssen, wie Sie ihn erstellen können. Beachten Sie, dass dies innerhalb eines unevaluierten Kontext nur dann möglich ist, wie zum Beispiel decltype, sizeofund andere.


Beachten Sie, dass dies decltypenicht unbedingt erforderlich ist, da sizeof(und alle nicht bewerteten Kontexte) diese Verbesserung erhalten haben. Es ist nur so, dass decltypebereits ein Typ geliefert wird und als solcher nur sauberer ist. Hier ist eine sizeofVersion 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 Parameter intund longsind aus demselben Grund immer noch vorhanden. Der Array-Zeiger wird verwendet, um einen Kontext bereitzustellen, in dem sizeofverwendet werden kann.


4
Der Vorteil von decltypeover sizeofbesteht auch darin, dass ein temporäres Element 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 ist eine Instanziierung einer Klassenvorlage).
Johannes Schaub - litb

5
Microsoft hat Expression SFINAE noch nicht in seinem C ++ - Compiler implementiert. Ich denke nur, ich könnte helfen, einigen Leuten Zeit zu sparen, da ich verwirrt war, warum dies bei mir nicht funktionierte. Gute Lösung, ich kann es kaum erwarten, sie in Visual Studio zu verwenden!
Jonathan

3
Ihr erster Beispiellink ist defekt
NathanOliver

1
Es muss gesagt werden, dass dies static_assert(has_stream<X, char>() == true, "fail X");kompiliert und nicht bestätigt wird, da char in int konvertierbar ist. Wenn dieses Verhalten also nicht erwünscht ist und alle Argumenttypen übereinstimmen sollen, weiß ich nicht, wie dies erreicht werden kann?
Gabriel

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

159

In C ++ kann SFINAE dafür verwendet werden (beachten Sie, dass dies mit C ++ 11-Funktionen einfacher ist, da es erweitertes SFINAE für nahezu beliebige Ausdrücke unterstützt - das Folgende wurde für die Verwendung mit gängigen C ++ 03-Compilern entwickelt):

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        typedef char yes[1];                                            \
        typedef char no [2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

Die obige Vorlage und das Makro versuchen, eine Vorlage zu instanziieren, indem sie einen Elementfunktionszeigertyp und den tatsächlichen Elementfunktionszeiger erhält. Wenn die Typen nicht passen, wird die Vorlage von SFINAE ignoriert. Verwendung wie folgt:

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

Beachten Sie jedoch, dass Sie diese toStringFunktion nicht einfach in diesem if-Zweig aufrufen können . Da der Compiler in beiden Zweigen die Gültigkeit überprüft, schlägt dies in Fällen fehl, in denen die Funktion nicht vorhanden ist. Eine Möglichkeit besteht darin, SFINAE erneut zu verwenden (enable_if kann auch über Boost abgerufen werden):

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

Viel Spaß damit. Der Vorteil davon ist, dass es auch für überladene Elementfunktionen und auch für konstante Elementfunktionen funktioniert (denken Sie daran, dann std::string(T::*)() constals Elementfunktionszeigertyp zu verwenden!).


7
Mir gefällt, wie verwendet type_checkwird, um sicherzustellen, dass die Signaturen genau übereinstimmen. Gibt es eine Möglichkeit, es so zu gestalten, dass es mit jeder Methode übereinstimmt, die so aufgerufen werden kann, wie eine Methode mit Signatur Signaufgerufen werden kann? (ZB wenn Sign= std::string(T::*)(), lassen Sie std::string T::toString(int default = 42, ...)übereinstimmen.)
j_random_hacker

5
Ich finde nur etwas heraus, das mir nicht sofort klar war, also für den Fall, dass 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.
SCFrench

3
@ deek0146: Ja, Tdarf kein primitiver Typ sein, da die Zeiger-auf-Methode-von-T-Deklaration nicht SFINAE unterliegt und für Nicht-Klasse-T-Fehler fehlerhaft ist. IMO Die einfachste Lösung besteht darin, sie mit is_classPrüfung von zu kombinieren Boost.
Jan Hudec

2
Wie kann ich diese toStringFunktion ausführen, wenn es sich um eine Vorlagenfunktion handelt?
Frank

4
Ist das (oder etwas Äquivalentes) in Boost?
Dan Nissenbaum

89

C ++ 20 - requiresAusdrücke

Mit C ++ 20 kommen Konzepte und verschiedene Tools wie requiresAusdrücke, die eine integrierte Methode sind, um die Existenz einer Funktion zu überprüfen. Mit ihnen können Sie Ihre optionalToStringFunktion wie folgt umschreiben :

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 für die Aufnahme in die C ++ 17-Standardbibliothek vor, das es schließlich in die Grundlagen der Bibliothek TS v2 geschafft hat. Es wird höchstwahrscheinlich nie in den Standard aufgenommen, da es seitdem durch requiresAusdrücke subsumiert wurde , aber es löst das Problem dennoch auf eine etwas elegante Art und Weise. Das Toolkit enthält einige Metafunktionen, mit std::is_detecteddenen Metafunktionen zur Typ- oder Funktionserkennung einfach darüber geschrieben werden können. So können 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 nicht getestet wurde. Das Erkennungs-Toolkit ist noch nicht in Standardbibliotheken verfügbar. Der Vorschlag enthält jedoch eine vollständige Implementierung, die Sie bei Bedarf problemlos kopieren können. Es spielt sich 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 speziellen Beispiel auf und bietet in seiner Dokumentation eine Lösung für C ++ 14, daher werde ich es direkt zitieren:

[...] Hana bietet eine is_validFunktion, 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()) { });

Dies lässt uns ein Funktionsobjekt zurück, has_toStringdas zurückgibt, ob der angegebene Ausdruck für das Argument gültig ist, das wir an ihn übergeben. Das Ergebnis wird als zurückgegeben IntegralConstant, daher ist die Konstanz 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_toStringsie an Algorithmen höherer Ordnung übergeben und auch im Funktionsumfang definiert werden können, 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 , das in Boost 1.54.0 eingeführt wurde. Für Ihr Beispiel müssten Sie das Makro verwenden BOOST_TTI_HAS_MEMBER_FUNCTION. So können 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önnen Sie mit die boolSFINAE-Prüfung erstellen.

Erläuterung

Das Makro BOOST_TTI_HAS_MEMBER_FUNCTIONgeneriert die Metafunktion, has_member_function_toStringdie den markierten Typ als ersten Vorlagenparameter verwendet. Der zweite Vorlagenparameter entspricht dem Rückgabetyp der Elementfunktion, und die folgenden Parameter entsprechen den Typen der Funktionsparameter. Das Mitglied valueenthält, trueob die Klasse Teine Mitgliedsfunktion hat std::string toString().

Alternativ has_member_function_toStringkann ein Elementfunktionszeiger als Vorlagenparameter verwendet werden. Daher ist es möglich, has_member_function_toString<T, std::string>::valuedurch zu ersetzen has_member_function_toString<std::string T::* ()>::value.


1
prägnanter als 03
ZFY

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

Ist die C ++ 20-Lösung wirklich gültig? Ich würde es mögen - aber es wird von g ++ und msvc abgelehnt - nur von clang akzeptiert.
Bernd Baumanns

Bei cppreference können Sie lesen: Wenn ein Anforderungsausdruck ungültige Typen oder Ausdrücke in seinen Anforderungen enthält und nicht in der Deklaration einer Entität mit Vorlagen enthalten ist, ist das Programm fehlerhaft.
Bernd Baumanns

@BerndBaumanns Wirklich? Ich habe es mit GCC-Trunk zum Laufen gebracht : godbolt.org/z/CBwZdE Vielleicht haben Sie Recht, ich habe nur überprüft, ob es funktioniert, aber nicht überprüft, ob es gemäß dem Standardtext legal ist.
Morwenn

56

Obwohl diese Frage zwei Jahre alt ist, werde ich es wagen, meine Antwort hinzuzufügen. Hoffentlich klärt es die vorherige, unbestreitbar ausgezeichnete Lösung. Ich nahm die sehr hilfreichen Antworten von Nicola Bonelli und Johannes Schaub und führte sie zu einer Lösung zusammen, die meiner Meinung nach besser lesbar und klar ist und keine typeofErweiterung erfordert :

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 Kredit geht hauptsächlich an Nicola Bonelli und Johannes Schaub, also stimmen Sie ab, wenn meine Antwort Ihnen hilft :)


1
Ich frage mich nur, ob dies etwas bewirkt, was Konrad Rudolphs Lösung unten nicht tut.
Alastair Irvine

3
@AlastairIrvine, diese Lösung verbirgt die gesamte Logik im Inneren. Konrad's belastet den Benutzer teilweise. Konrads Lösung ist zwar kurz und viel besser lesbar, erfordert jedoch eine separate Vorlagenspezialisierung für jede Klasse toString. Wenn Sie eine generische Bibliothek schreiben, die mit einer beliebigen Klasse da draußen arbeiten möchte (denken Sie an etwas wie Boost), kann es inakzeptabel sein, dass der Benutzer zusätzliche Spezialisierungen für einige obskure Vorlagen definiert. Manchmal ist es vorzuziehen, einen sehr komplizierten Code zu schreiben, um die öffentliche Schnittstelle so einfach wie möglich zu halten.
FireAphis

30

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

4
Dies ist einfach und elegant, beantwortet aber streng genommen nicht die Frage von OP: Sie ermöglichen dem Aufrufer nicht, die Existenz einer Funktion zu überprüfen , sondern geben sie immer an . Aber trotzdem schön.
Adrian W

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

Falls es jemand anderem hilft, könnte ich diese Arbeit nicht ohne template<typename>die variable Überlastung machen: Es wurde nicht für die Lösung in Betracht gezogen.
Laboratorio Cobotica

Auch dies ist ungültig C ++ 11.
Peter

29

Dafür gibt es Typmerkmale. Leider müssen sie manuell definiert werden. Stellen Sie sich in Ihrem Fall 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;
}

5
Sie sollten Enum für Merkmale anstelle von statischen Konstanten bevorzugen: "Statische Konstantenelemente sind l-Werte, wodurch der Compiler gezwungen wird, die Definition für das statische Element zu instanziieren und zuzuweisen. Daher ist die Berechnung nicht mehr auf eine reine Kompilierungszeit beschränkt "Wirkung."
Özgür

5
"Aufzählungswerte sind keine l-Werte (dh sie haben keine Adresse). Wenn Sie sie also" als Referenz "übergeben, wird kein statischer Speicher verwendet. Es ist fast genau so, als hätten Sie den berechneten Wert als Literal übergeben Diese Überlegungen motivieren uns, Aufzählungswerte zu verwenden. "C ++ - Vorlagen: Der vollständige Leitfaden
Özgür

22
Steuerung: Nein, die zitierte Passage gilt hier nicht, da statische Konstanten vom Integer-Typ ein Sonderfall sind! Sie verhalten sich hier genau wie eine Aufzählung und sind der bevorzugte Weg. Der alte Enum-Hack war nur für Compiler erforderlich, die nicht dem C ++ - Standard entsprachen.
Konrad Rudolph

3
@ Roger Pate: Nicht ganz. "Im Programm verwendet" ist hier anscheinend gleichbedeutend mit "referenziert". Die vorherrschende Lesart dieser Passage und die von allen modernen C ++ - Compilern implementierte ist, dass Sie den Wert einer statischen Konstante annehmen können, ohne ihn deklarieren zu müssen (der vorherige Satz sagt dies: „… das Mitglied kann in integralen Konstantenausdrücken erscheinen … ”). Sie müssen es nur definieren, wenn Sie seine Adresse verwenden (explizit über &T::xoder implizit durch Binden an eine Referenz).
Konrad Rudolph


25

Nun, diese Frage hat bereits eine lange Liste von Antworten, aber ich möchte den Kommentar von Morwenn hervorheben: 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 Folgendes.

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

// 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 (einen Typ, einen Elementtyp, eine Funktion, eine Elementfunktion usw.). Im Fall des 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 aus N4502 zeigt eine ausführlichere 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 ziemlich einfach: Ein reduzierter Satz von Werkzeugen ( void_tund detect) reicht aus, ohne dass haarige Makros erforderlich sind. Außerdem wurde berichtet (siehe N4502 ), dass es messbar effizienter ist (Kompilierungszeit 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 dazu führte void_t, dass es nicht wie erwartet funktionierte. Yakk hat die Problemumgehung bereits bereitgestellt: Verwenden Sie die folgende Definition von void_t( void_t in der Parameterliste funktioniert, jedoch 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

Ist es möglich, es zu erweitern, um Funktionen zu erkennen, die keine Mitglieder sind?
Plasmacel

Ja sicher. Schauen Sie sich die Beispiele genau an: Sie geben im Grunde einen Ausdruck an und prüfen, ob er gültig ist. Nichts erfordert, dass dieser Ausdruck nur einen Elementfunktionsaufruf betrifft.
Akim

N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf ) ist der Weg der Zukunft ... Ich suchte nach einer guten Möglichkeit, Dinge an Typen zu erkennen, und N4502 ist der Weg gehen.
Tlonuk

11

Dies ist eine C ++ 11-Lösung für das allgemeine Problem, wenn "Wenn ich X machen würde, würde es kompilieren?"

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
  T,
  type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};

Merkmal has_to_string, has_to_string<T>::valuedas truegenau dann Tist, wenn es eine Methode .toStringgibt, die in diesem Zusammenhang mit 0 Argumenten aufgerufen werden kann.

Als nächstes würde ich Tag Dispatching verwenden:

namespace details {
  template<class T>
  std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
    return obj->toString();
  }
  template<class T>
  std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
    return "toString not defined";
  }
}
template<class T>
std::string optionalToString(T* obj) {
  return details::optionalToString_helper( obj, has_to_string<T>{} );
}

Dies ist tendenziell wartbarer als komplexe SFINAE-Ausdrücke.

Sie können diese Merkmale mit einem Makro schreiben, wenn Sie es häufig tun, aber sie sind relativ einfach (jeweils ein paar Zeilen), sodass es sich möglicherweise nicht lohnt:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

Mit den obigen Anweisungen wird ein Makro erstellt MAKE_CODE_TRAIT. Sie übergeben ihm den Namen des gewünschten Merkmals und einen Code, mit dem der Typ getestet werden kann T. Somit:

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

erstellt die oben genannte Merkmalsklasse.

Abgesehen davon ist die obige Technik Teil dessen, was MS als "Ausdruck SFINAE" bezeichnet, und ihr 2013er Compiler schlägt ziemlich schwer fehl.

Beachten Sie, dass in C ++ 1y die folgende Syntax möglich ist:

template<class T>
std::string optionalToString(T* obj) {
  return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
    return obj.toString();
  }) *compiled_else ([&]{ 
    return "toString not defined";
  });
}

Dies ist ein bedingter Inline-Kompilierungszweig, der viele C ++ - Funktionen missbraucht. Dies ist es wahrscheinlich nicht wert, da der Vorteil (dass Code inline ist) nicht die Kosten wert ist (so gut wie niemand versteht, wie es funktioniert), aber die Existenz dieser oben genannten Lösung kann von Interesse sein.


Behandelt dies private Fälle?
Turm 120

@ turm120 Ich müsste experimentieren: Wie Vorlagen mit privat / öffentlich / geschützt interagieren, ist für mich etwas dunkel. Es spielt jedoch keine Rolle, wo Sie aufrufen has_to_string.
Yakk - Adam Nevraumont

Aber Sie wissen, wenn Sie von der anderen Seite schauen ... Wir können geschützte Mitglieder aus der abgeleiteten Klasse erreichen. Vielleicht, wenn Sie all dieses Zeug in die INSIDE-Klasse setzen und von Strukturen in Constexpr-Funktionen konvertieren ...
Tower120

Hier, schauen Sie sich diese coliru.stacked-crooked.com/a/ee94d16e7c07e093 Ich kann es einfach nicht constexpr machen
Turm120

@ Tower120 C ++ 1y macht es funktioniert: coliru.stacked-crooked.com/a/d8cdfff24a171394
Yakk - Adam Nevraumont

10

Hier sind einige Verwendungsausschnitte: * Der Mut für all dies ist weiter unten

Suchen Sie nach Mitgliedern xin einer bestimmten Klasse. Könnte var, func, class, union oder enum sein:

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

Auf Mitgliedsfunktion prüfen 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;

Auf Mitgliedsvariable prüfen x:

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

Nach Mitgliedsklasse suchen x:

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

Auf Mitgliedsgewerkschaft prüfen x:

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

Auf Mitgliederliste prüfen x:

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

Überprüfen Sie xunabhängig von der Signatur, ob ein Mitglied funktioniert :

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)

1
Haben Sie eine Idee, warum, wenn wir sig_check<func_sig, &T::func_name>zur freien Funktionsprüfung wechseln : sig_check<func_sig, &func_name>Es kann nicht mit einer "nicht deklarierten Kennung" erstellt werden, in der der Name der zu prüfenden Funktion angegeben ist? weil ich erwarten würde, dass SFINAE es NICHT zu einem Fehler macht, tut es genau das für Mitglieder, warum nicht für kostenlose Funktionen?
v.oddou

Ich nehme an, es hätte etwas damit zu tun, dass eine freie Funktion keine Klasse oder Struktur ist. Diese Technik zum Ableiten der Anwesenheit eines Mitglieds konzentriert sich wirklich auf den Mehrfachvererbungsmechanismus in C ++, der die Mehrdeutigkeit zwischen einer Stub-Klasse erzwingt, die nur zum Hosten des Mitglieds existiert, nach dem Sie suchen, und der Klasse, die Sie tatsächlich nach dem Mitglied suchen Das ist eine interessante Frage, hatte aber nicht darüber nachgedacht. Sie könnten sich nach anderen C ++ 11/14 Member-Check-Techniken umsehen. Ich habe einige clevere Dinge im neuen Standard gesehen.
Brett Rossier

Vielen Dank für Ihre Antwort. Ich denke, ich muss möglicherweise eingehender prüfen, welche Informationen Sie über die Vererbung geben, da ich bis jetzt keinen Zusammenhang zwischen dem einfachen Verlassen auf SFINAE gesehen habe, um einen Ausdruck zu erstellen, der den Zugriff nicht korrekt ausdrückt ein Mitglied in einem Vorlagentyp-Parameter und Mehrfachvererbung. Aber ich bin fest davon überzeugt, dass in C ++ auch entfernte Konzepte aufeinander bluten können. Für kostenlose Funktionen ist diese Frage jetzt interessant: stackoverflow.com/questions/26744589 Die TC-Antwort scheint einen Trick zu verwenden, einen Dummy zu deklarieren, um den "nicht deklarierten Bezeichner" zu vermeiden
v.oddou

8

Ich habe eine Antwort darauf in einem anderen Thread geschrieben, der (im Gegensatz zu den obigen Lösungen) auch geerbte Mitgliedsfunktionen überprüft:

SFINAE, um nach geerbten Elementfunktionen zu suchen

Hier einige Beispiele aus dieser Lösung:

Beispiel 1:

Wir suchen nach einem Mitglied mit folgender Unterschrift: T::const_iterator begin() const

template<class T> struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U> 
    static Yes test(U const * data, 
                    typename std::enable_if<std::is_same<
                             typename U::const_iterator, 
                             decltype(data->begin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

Bitte beachten Sie, dass es sogar die Konstanz der Methode überprüft und auch mit primitiven Typen funktioniert. (Ich meine, has_const_begin<int>::valueist falsch und verursacht keinen Fehler bei der Kompilierung.)

Beispiel 2

Jetzt suchen wir nach der Unterschrift: void foo(MyClass&, unsigned)

template<class T> struct has_foo
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U>
    static Yes test(U * data, MyClass* arg1 = 0,
                    typename std::enable_if<std::is_void<
                             decltype(data->foo(*arg1, 1u))
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};

Bitte beachten Sie, dass MyClass nicht standardmäßig konstruierbar sein oder ein spezielles Konzept erfüllen muss. Die Technik funktioniert auch mit Vorlagenelementen.

Ich warte gespannt auf Meinungen dazu.


7

Das war ein schönes kleines Puzzle - tolle Frage!

Hier ist eine Alternative zu Nicola Bonellis Lösung , die nicht auf dem nicht standardmäßigen typeofOperator basiert .

Leider funktioniert es nicht unter GCC (MinGW) 3.4.5 oder Digital Mars 8.42n, aber unter allen Versionen von MSVC (einschließlich VC6) und unter Comeau C ++.

Der längere Kommentarblock enthält Details dazu, wie es funktioniert (oder funktionieren soll). Wie es heißt, bin ich mir nicht sicher, welches Verhalten den Standards entspricht - ich würde einen Kommentar dazu begrüßen.


Update - 7. November 2008:

Es sieht so aus, als ob dieser Code zwar syntaktisch korrekt ist, das Verhalten von MSVC und Comeau C ++ jedoch nicht dem Standard entspricht (danke an Leon Timmermans und litb, die mich in die richtige Richtung gelenkt haben ). Der C ++ 03-Standard sagt Folgendes aus:

14.6.2 Abhängige Namen [temp.dep]

Absatz 3

Wenn bei der Definition einer Klassenvorlage oder eines Mitglieds einer Klassenvorlage eine Basisklasse der Klassenvorlage von einem Vorlagenparameter abhängt, wird der Bereich der Basisklasse auch bei der Suche nach unqualifizierten Namen zum Zeitpunkt der Definition der Klasse nicht untersucht Vorlage oder Mitglied oder während einer Instanziierung der Klassenvorlage oder des Mitglieds.

Es sieht also so aus, als ob MSVC oder Comeau die toString()Mitgliedsfunktion zum TDurchführen einer Namenssuche an der Aufrufstelle in Betracht ziehen, doToString()wenn die Vorlage instanziiert wird. Dies ist falsch (obwohl es tatsächlich das Verhalten ist, nach dem ich in diesem Fall gesucht habe).

Das Verhalten von GCC und Digital Mars scheint korrekt zu sein - in beiden Fällen ist die Nichtmitgliedsfunktion toString()an den Aufruf gebunden.

Ratten - Ich dachte, ich hätte vielleicht eine clevere Lösung gefunden, stattdessen habe ich ein paar Compiler-Fehler entdeckt ...


#include <iostream>
#include <string>

struct Hello
{
    std::string toString() {
        return "Hello";
    }
};

struct Generic {};


// the following namespace keeps the toString() method out of
//  most everything - except the other stuff in this
//  compilation unit

namespace {
    std::string toString()
    {
        return "toString not defined";
    }

    template <typename T>
    class optionalToStringImpl : public T
    {
    public:
        std::string doToString() {

            // in theory, the name lookup for this call to 
            //  toString() should find the toString() in 
            //  the base class T if one exists, but if one 
            //  doesn't exist in the base class, it'll 
            //  find the free toString() function in 
            //  the private namespace.
            //
            // This theory works for MSVC (all versions
            //  from VC6 to VC9) and Comeau C++, but
            //  does not work with MinGW 3.4.5 or 
            //  Digital Mars 8.42n
            //
            // I'm honestly not sure what the standard says 
            //  is the correct behavior here - it's sort 
            //  of like ADL (Argument Dependent Lookup - 
            //  also known as Koenig Lookup) but without
            //  arguments (except the implied "this" pointer)

            return toString();
        }
    };
}

template <typename T>
std::string optionalToString(T & obj)
{
    // ugly, hacky cast...
    optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);

    return temp->doToString();
}



int
main(int argc, char *argv[])
{
    Hello helloObj;
    Generic genericObj;

    std::cout << optionalToString( helloObj) << std::endl;
    std::cout << optionalToString( genericObj) << std::endl;
    return 0;
}

1
Nein, es ist nicht standardkonform, obwohl ich denke, dass es in GCC funktioniert, wenn Sie die Option -fpermissive aktivieren.
Leon Timmermans

Ich weiß, dass die Kommentare nicht viel Platz bieten, aber können Sie auf Informationen verweisen, warum sie nicht den Standards entsprechen? (Ich streite nicht - ich bin neugierig)
Michael Burr

Mike B: Der Standard sagt in 3.10 S. 15: "Wenn ein Programm versucht, über einen anderen Wert als einen der folgenden Typen auf den gespeicherten Wert eines Objekts zuzugreifen, ist das Verhalten undefiniert", und diese Liste enthält in der Tat nicht den Fall, den Sie haben tun.
Johannes Schaub - litb

4
Ich bin mir nicht sicher, warum es keinen weiteren Kommentar von mir hinzufügt: Ihr toString-Anruf ist nicht qualifiziert. Daher wird immer die freie Funktion aufgerufen und niemals die in der Basis, da die Basisklasse von einem Vorlagentypparameter abhängig ist.
Johannes Schaub - Litb

@litb: Danke für die Hinweise. Ich denke nicht, dass 3.10 hier gilt. Der Aufruf von toString () innerhalb von doToString () bedeutet nicht "Zugriff auf den gespeicherten Wert eines Objekts über einen l-Wert". Aber dein 2. Kommentar ist richtig. Ich werde die Antwort aktualisieren.
Michael Burr

6

Die hier von litb vorgestellte Standard-C ++ - Lösung funktioniert nicht wie erwartet, wenn die Methode zufällig in einer Basisklasse definiert ist.

Eine Lösung für diese Situation finden Sie unter:

Auf Russisch: http://www.rsdn.ru/forum/message/2759773.1.aspx

Englische Übersetzung von Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

Es ist wahnsinnig schlau. Ein Problem bei dieser Lösung besteht jedoch darin, dass Compilerfehler auftreten, wenn der zu testende Typ nicht als Basisklasse verwendet werden kann (z. B. primitive Typen).

In Visual Studio ist mir aufgefallen, dass bei der Arbeit mit Methoden ohne Argumente ein zusätzliches Paar redundanter () um die Argumente eingefügt werden muss, um () in der Größe des Ausdrucks abzuleiten.


Hmm, nachdem ich meine eigene Version mit diesen Ideen entwickelt hatte, stellte ich fest, dass die Idee einige andere Nachteile hat, also entfernte ich den Code wieder aus meiner Antwort. Zum einen müssen alle Funktionen im Zieltyp öffentlich sein. Sie können hier also nicht nach einer "f" -Funktion suchen: struct g { void f(); private: void f(int); };weil eine der Funktionen privat ist (dies liegt daran, dass der Code dies tut using g::f;, wodurch er fehlschlägt, wenn auf eine fnicht zugegriffen werden kann).
Johannes Schaub - litb

6

MSVC verfügt über die Schlüsselwörter __if_exists und __if_not_exists ( Doc ). Zusammen mit dem Typ-SFINAE-Ansatz von Nicola konnte ich einen Check für GCC und MSVC erstellen, wie es das OP suchte.

Update: Quelle finden Sie hier


6

Ein Beispiel mit SFINAE und Template-Teilspezialisierung durch Schreiben einer Has_fooKonzeptprüfung:

#include <type_traits>
struct A{};

struct B{ int foo(int a, int b);};

struct C{void foo(int a, int b);};

struct D{int foo();};

struct E: public B{};

// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;

template<typename T, typename = void> struct Has_foo: std::false_type{};

template<typename T> 
struct Has_foo<T, void_t<
    std::enable_if_t<
        std::is_same<
            int, 
            decltype(std::declval<T>().foo((int)0, (int)0))
        >::value
    >
>>: std::true_type{};


static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");

5

Ich habe die in https://stackoverflow.com/a/264088/2712152 bereitgestellte Lösung geändert , um sie etwas allgemeiner zu gestalten. Da es keine der neuen C ++ 11-Funktionen verwendet, können wir es auch mit alten Compilern verwenden und sollten auch mit msvc funktionieren. Die Compiler sollten es C99 jedoch ermöglichen, dies zu verwenden, da es verschiedene Makros verwendet.

Mit dem folgenden Makro können Sie überprüfen, ob eine bestimmte Klasse ein bestimmtes typedef hat oder nicht.

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Das folgende Makro kann verwendet werden, um zu überprüfen, ob eine bestimmte Klasse eine bestimmte Elementfunktion mit einer bestimmten Anzahl von Argumenten hat oder nicht.

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Wir können die obigen 2 Makros verwenden, um die Prüfungen für has_typedef und has_mem_func wie folgt durchzuführen:

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}

Sie können dies verbessern, um Elementfunktionen mit Vorlagenargumenten zu unterstützen. Ändern Sie die Vorlage <Typname T> in die Vorlage <Typname T, Typname ... Args>. Anschließend können Sie in Ihrer Makroelipse "Args ..." verwenden, um eine Prüfstruktur mit verschiedenen Vorlagenargumenten zu erstellen. z.B. Erkennen Sie die Methode "void onNext (const T &)" HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... ); ...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
ACyclic

4

Seltsamerweise schlug niemand den folgenden schönen Trick vor, den ich einmal auf dieser Seite 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 Mehrdeutigkeit bei der Suche nach foo ein Substitutionsfehler ist. Ich habe es auf gcc zum Laufen gebracht, bin mir aber nicht sicher, ob es Standard ist.


3

Die generische Vorlage, mit der überprüft werden kann, ob eine "Funktion" 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 foogibt, die mit der Signatur kompatibel istdouble(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


Gibt es eine Möglichkeit, den has_fooin den Template-Aufruf von einzubinden is_supported. Was ich möchte, ist etwas zu nennen wie : std::cout << is_supported<magic.foo(), struct1>::value << std::endl;. Der Grund dafür ist, dass ich has_foofür jede unterschiedliche Funktionssignatur, die ich überprüfen möchte, eine definieren möchte, bevor ich nach der Funktion suchen kann.
CJCombrink

2

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

Schlägt fehl, wenn toStringes überlastet ist, da &U::toStringes nicht eindeutig ist.
Yakk - Adam Nevraumont

@ Yakk Ich denke, eine Besetzung kann dieses Problem beheben.
user1095108

2

Hier gibt es viele Antworten, aber ich konnte keine Version finden, die eine echte Reihenfolge der Methodenauflösung ausführt , ohne eine der neueren C ++ - Funktionen zu verwenden (nur mit C ++ 98-Funktionen).
Hinweis: Diese Version wurde getestet und funktioniert mit vc ++ 2013, g ++ 5.2.0 und dem Onlline-Compiler.

Also habe ich eine Version entwickelt, 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 Überprüfung des Rückgabetyps und vc ++ 2010-Problemumgehung): http://cpp.sh/5b2vs

Keine Quelle, da ich es mir selbst ausgedacht habe.

Beachten Sie beim Ausführen der Live-Demo auf dem g ++ - Compiler, dass Arraygrößen von 0 zulässig sind. Dies bedeutet, dass der verwendete static_assert keinen Compilerfehler auslöst, selbst wenn er fehlschlägt.
Eine häufig verwendete Problemumgehung besteht darin, das 'typedef' im Makro durch 'extern' zu ersetzen.


Nein, aber ich erkläre es selbst und es wird kein rvalue verwendet (siehe oben in meinem Code). Oder Sie können sich einfach selbst überzeugen und die Live-Demo im c ++ 98-Modus ausprobieren. PS: static_assert ist auch nicht c ++ 98, aber es gibt
Workarounds

d'oh! habe das verpasst. :-)
Ian Ni-Lewis

Ihre statischen Zusicherungen funktionieren nicht. Sie müssen die Array-Größe -1 anstelle von 0 verwenden (versuchen Sie es mit Putting static_assert(false);). Ich habe dies in Verbindung mit CRTP verwendet, um festzustellen, ob die abgeleitete Klasse eine bestimmte Funktion hat - was sich als nicht funktionierend herausstellt, aber Ihre Behauptungen wurden immer bestanden. Ich habe ein paar Haare verloren.
das Schwein

Ich gehe davon aus, dass Sie g ++ verwenden. Bitte beachten Sie, dass gcc / g ++ eine Erweiterung hat, die ein Array mit der Größe Null zulässt ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
user3296587

Könnten Sie dies möglicherweise umschreiben, um den Operator nicht zu überlasten? zB einen anderen Operator wählen? Vermeiden Sie auch die Verschmutzung des Namespace mit etwas anderem als has_awesome_member?
Einpoklum

1

Hier ist meine Version, die alle möglichen Überladungen von Elementfunktionen mit beliebiger Arität behandelt, einschließlich Vorlagenelementfunktionen, möglicherweise mit Standardargumenten. Es werden drei sich gegenseitig ausschließende Szenarien unterschieden, wenn ein Elementfunktionsaufruf einen Klassentyp mit bestimmten Argumenttypen ausführt: (1) gültig oder (2) mehrdeutig oder (3) nicht realisierbar. Anwendungsbeispiel:

#include <string>
#include <vector>

HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)

struct test
{
   void bar(int);
   void bar(double);
   void bar(int,double);

   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   bar(const T&, int=0){}

   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   bar(const std::vector<T>&, T*){}

   template < typename T >
   int bar(const std::string&, int){}
};

Jetzt können Sie es so verwenden:

int main(int argc, const char * argv[])
{
   static_assert( has_mem_bar<test>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
   static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
   static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
   static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
   static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
   static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");

   static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
   static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");

   static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");

   return 0;
}

Hier ist der in C ++ 11 geschriebene Code. Sie können ihn jedoch problemlos (mit geringfügigen Änderungen) auf Nicht-C ++ 11 portieren, das über Erweiterungen verfügt (z. B. gcc). Sie können das Makro HAS_MEM durch Ihr eigenes ersetzen.

#pragma once

#if __cplusplus >= 201103

#include <utility>
#include <type_traits>

#define HAS_MEM(mem)                                                                                     \
                                                                                                     \
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  struct ambiguate_seed { char mem; };                                                               \
  template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
                                                                                                     \
  template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
  template < typename                                 > static constexpr yes test(...);              \
                                                                                                     \
  static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
  typedef std::integral_constant<bool,value>    type;                                                \
};


#define HAS_MEM_FUN_CALL(memfun)                                                                         \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  template < typename U, bool = has_mem_##memfun<U>::value >                                         \
  struct impl                                                                                        \
  {                                                                                                  \
     template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
     struct test_result { using type = yes; };                                                       \
                                                                                                     \
     template < typename V > static constexpr typename test_result<V>::type test(int);               \
     template < typename   > static constexpr                            no test(...);               \
                                                                                                     \
     static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
     using type = std::integral_constant<bool, value>;                                               \
  };                                                                                                 \
                                                                                                     \
  template < typename U >                                                                            \
  struct impl<U,false> : std::false_type {};                                                         \
                                                                                                     \
  static constexpr bool value = impl<T>::value;                                                      \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
  struct ambiguate_seed { void memfun(...); };                                                       \
                                                                                                     \
  template < class U, bool = has_mem_##memfun<U>::value >                                            \
  struct ambiguate : U, ambiguate_seed                                                               \
  {                                                                                                  \
    using ambiguate_seed::memfun;                                                                    \
    using U::memfun;                                                                                 \
  };                                                                                                 \
                                                                                                     \
  template < class U >                                                                               \
  struct ambiguate<U,false> : ambiguate_seed {};                                                     \
                                                                                                     \
  static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
  static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
                             or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
  static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};

#endif


1

Sie können die gesamte Metaprogrammierung in C ++ 14 überspringen und diese einfach mit fit::conditionalaus der Fit- Bibliothek schreiben :

template<class T>
std::string optionalToString(T* x)
{
    return fit::conditional(
        [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
        [](auto*) { return "toString not defined"; }
    )(x);
}

Sie können die Funktion auch direkt aus den Lambdas erstellen:

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
    [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
    [](auto*) -> std::string { return "toString not defined"; }
);

Wenn Sie jedoch einen Compiler verwenden, der keine generischen Lambdas unterstützt, müssen Sie separate Funktionsobjekte schreiben:

struct withToString
{
    template<class T>
    auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
    {
        return obj->toString();
    }
};

struct withoutToString
{
    template<class T>
    std::string operator()(T*) const
    {
        return "toString not defined";
    }
};

FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
    withToString(),
    withoutToString()
);

1
Wie einfach ist es, dies zu schreiben, um nicht von fiteiner anderen Bibliothek als dem Standard abhängig zu sein ?
Einpoklum

1

Mit C ++ 20 können Sie Folgendes schreiben:

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}

0

Hier ist ein Beispiel für den Arbeitscode.

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

template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
    return obj->toString();
}

template <class T>
std::string optionalToString(const T* obj, long)
{
    return "toString not defined";
}

int main()
{
    A* a;
    B* b;

    std::cout << optionalToString(a, 0) << std::endl; // This is A
    std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}

toStringFn<T>* = nullptraktiviert die Funktion, die ein zusätzliches intArgument akzeptiert, das Vorrang vor der Funktion hat, die longbeim Aufruf mit verwendet wird 0.

Sie können dasselbe Prinzip für die Funktionen verwenden, die zurückgegeben werden, truewenn die Funktion implementiert ist.

template <typename T>
constexpr bool toStringExists(long)
{
    return false;
}

template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
    return true;
}


int main()
{
    A* a;
    B* b;

    std::cout << toStringExists<A>(0) << std::endl; // true
    std::cout << toStringExists<B>(0) << std::endl; // false
}

0

Ich hatte ein ähnliches Problem:

Eine Vorlagenklasse, die möglicherweise von wenigen Basisklassen abgeleitet ist, von denen einige ein bestimmtes Mitglied haben und andere nicht.

Ich habe es ähnlich wie die Antwort "typeof" (Nicola Bonelli) gelöst, aber mit decltype, damit es auf MSVS kompiliert und korrekt ausgeführt wird:

#include <iostream>
#include <string>

struct Generic {};    
struct HasMember 
{
  HasMember() : _a(1) {};
  int _a;
};    

// SFINAE test
template <typename T>
class S : public T
{
public:
  std::string foo (std::string b)
  {
    return foo2<T>(b,0);
  }

protected:
  template <typename T> std::string foo2 (std::string b, decltype (T::_a))
  {
    return b + std::to_string(T::_a);
  }
  template <typename T> std::string foo2 (std::string b, ...)
  {
    return b + "No";
  }
};

int main(int argc, char *argv[])
{
  S<HasMember> d1;
  S<Generic> d2;

  std::cout << d1.foo("HasMember: ") << std::endl;
  std::cout << d2.foo("Generic: ") << std::endl;
  return 0;
}

0

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

Schreiben Sie es einmal und verwenden Sie es mehrmals. Es sind keine has_something<T>Klassen für Typmerkmale erforderlich .

#include <type_traits>

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

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

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

Beispiel

#include <iostream>

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

struct Example2 {
    int X;
};

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

int main() {
    static_assert(IS_VALID(Example, Foo));
    static_assert(IS_VALID(Example, Bar()));
    static_assert(!IS_VALID(Example, ZFoo));
    static_assert(!IS_VALID(Example, ZBar()));

    Example e1;
    Example2 e2;

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

-1
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}

6
"Wir brauchen keine Antwortbeschreibungen" ... fügen Sie Ihrer Antwort eine informative Beschreibung hinzu, um sie zu verbessern. Vielen Dank.
YesThatIsMyName
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.