Frage

Ist es möglich, eine Vorlage zu schreiben, die das Verhalten ändert, je nachdem, ob eine bestimmte Mitgliedsfunktion in einer Klasse definiert ist?

Hier ist ein einfaches Beispiel für das, 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 weiß, ist der Teil "function_exists".

War es hilfreich?

Lösung

Ja, mit Sfinae können Sie überprüfen, ob eine bestimmte Klasse eine bestimmte Methode bereitstellt. 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 für andere Plattformen tragbar ist, die verschiedene Compiler ausführen.

Andere Tipps

Diese Frage ist alt, aber mit C ++ 11 haben wir eine neue Möglichkeit, nach einer Funktion zu überprüfen (oder das Bestehen eines Nicht-Typ-Mitglieds), das sich erneut auf Sfinae stützt:

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 benutze ich Ausdruck Sfinae um das auszuschließen serialize(_imp) Funktionen von Überlastauflösung, wenn der erste Ausdruck im Inneren decltype Ist nicht gültig (auch bekannt als die Funktion existiert nicht).

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

Das 0 Argument wird verwendet, um die zu bevorzugen os << obj Überlastung, wenn beide verfügbar sind (wörtlich 0 ist vom Typ int Und als solches passt die erste Überlastung ein besseres Spiel).


Jetzt möchten Sie wahrscheinlich, dass ein Merkmal prüft, ob eine Funktion besteht. Zum Glück ist es einfach, das zu schreiben. Beachten Sie jedoch, dass Sie ein Merkmal schreiben müssen dich selbst Für jeden verschiedenen Funktionsnamen, den Sie möchten.

#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 ist im Grunde genommen das gleiche wie das 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(...) Erben von beiden std::true_type oder std::false_type Am Ende, je nachdem, ob die decltype Check-In test_stream versagt oder nicht.
Letzte, std::declval Gibt Ihnen einen "Wert" für jeden Typ, den Sie bestehen, ohne dass Sie wissen müssen, wie Sie ihn konstruieren können. Beachten Sie, dass dies nur in einem nicht evaluierten Kontext möglich ist, wie z. decltype, sizeof und andere.


Beachten Sie, dass decltype wird nicht unbedingt benötigt, wie sizeof (und alle unbestimmten Kontexte) haben diese Verbesserung erhalten. Es ist nur so dass decltype liefert bereits einen Typ und als solches ist nur sauberer. Hier ist ein sizeof Version einer der Überlastungen:

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

Das int und long Parameter sind aus dem gleichen Grund noch vorhanden. Der Array -Zeiger wird verwendet, um einen Kontext zu liefern, in dem sizeof kann verwendet werden.

C ++ erlaubt Sfinae Um dafür verwendet zu werden (Beachten Sie, dass dies mit C ++ - 11 -Funktionen einfacher ist, da es erweiterte SFINAE bei nahezu willkürlichen Ausdrücken unterstützt - das folgende wurde so gestaltet, dass sie mit gemeinsamen C ++ 03 -Compilern gearbeitet wurden):

#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, wodurch ein Mitgliedsfunktionszeigertyp und der tatsächliche Zeiger der Mitgliedsfunktion vergeben werden. Wenn die Typen nicht passen, wird die Vorlage ignoriert. Nutzung wie diese:

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 das nicht einfach nennen können toString Funktionen Sie in der Branche. Da der Compiler in beiden Zweigen auf Gültigkeit prüft, würde dies für Fälle fehlschlagen, die nicht vorhanden ist. Eine Möglichkeit besteht darin, Sfinae noch einmal zu verwenden (enable_if kann auch von Boost erhalten 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ß mit der Verwendung. Der Vorteil davon ist, dass es auch für überlastete Mitgliederfunktionen und auch für CONT -Mitgliedsfunktionen funktioniert (denken Sie daran, die Verwendung zu verwenden std::string(T::*)() const Als Mitgliedsfunktion Zeigertyp dann!).

Obwohl diese Frage zwei Jahre alt ist, werde ich es wagen, meine Antwort hinzuzufügen. Hoffentlich wird es die vorherige, unbestreitbar hervorragende Lösung verdeutlicht. Ich nahm die sehr hilfreichen Antworten von Nicola Bonelli und Johannes Schaub und fusionierte sie zu einer Lösung, nämlich, imho, lesbarer, klarer und erfordert nicht die 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 Kredit geht hauptsächlich an Nicola Bonelli und Johannes Schaub. Gib ihnen also eine Abstimmung, wenn meine Antwort dir hilft :)

C ++ 20 - requires Ausdrücke

Mit C ++ 20 kommen Konzepte und verschiedene Tools wie z. requires Ausdrücke die ein integrierter Weg sind, um eine Funktion zu überprüfen. Mit Tehm könnten Sie Ihre umschreiben optionalToString Funktion 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 vor, dass eine Erkennung für die Einbeziehung in die C ++ 17 -Standardbibliothek eingenommen wurde, die das Problem auf etwas elegante Weise lösen kann. Darüber hinaus wurde es gerade in die Grundlagen der Bibliothek TS V2 aufgenommen. Es führt einige Metafunktionen ein, einschließlich std::is_detected Dies kann verwendet werden, um einfach Typen oder Funktionserkennungsmetafunktionen oben zu schreiben. 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 ist. Das Erkennungs -Toolkit ist in Standardbibliotheken noch nicht verfügbar, aber der Vorschlag enthält eine vollständige Implementierung, die Sie problemlos kopieren können, wenn Sie sie wirklich benötigen. 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";
}

Boost.tti

Ein anderes etwas idiomatisches Toolkit, um einen solchen Scheck auszuführen - obwohl 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ö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önnten Sie die verwenden bool So erstellen Sie einen Sfinae -Check.

Erläuterung

Das Makro BOOST_TTI_HAS_MEMBER_FUNCTION erzeugt die Metafunktion has_member_function_toString Dies nimmt den geprüften Typ als erster Vorlageparameter ein. Der zweite Vorlagenparameter entspricht dem Rückgabetyp der Mitgliedsfunktion, und die folgenden Parameter entsprechen den Typen der Funktionsparameter 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 Mitgliedsfunktionszeiger als Vorlagenparameter nehmen. Daher ist es möglich zu ersetzen has_member_function_toString<T, std::string>::value durch has_member_function_toString<std::string T::* ()>::value.

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

Dafür gibt es Merkmale. 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;
}

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 ihn sehr viel einfacher macht. Sehen N4502 Einzelheiten, aber als eigenständiges Beispiel finden Sie Folgendes.

Dieser Teil ist der ständige Teil, in einen Kopfball gesteckt.

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

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

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

Dann gibt es den variablen Teil, in dem Sie angeben, wonach Sie suchen (ein Typ, einen Mitgliedstyp, eine Funktion, eine Mitgliedsfunktion 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, genommen von N4502, zeigt eine aufwändigere Sonde:

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

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

Im Vergleich zu den anderen oben beschriebenen Implementierungen ist diese ziemlich einfach: ein reduzierter Satz von Tools (void_t und detect) reicht keine Notwendigkeit für haarige Makros. 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, der verursachte void_t nicht wie erwartet arbeiten. Yakk stellte bereits die Arbeit zur Verfügung: Verwenden Sie die folgende Definition von void_t (void_t in der Parameterliste funktioniert, aber nicht als Rückkehrtyp):

#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

Dies ist eine C ++ 11 -Lösung für das allgemeine Problem, wenn "wenn ich x hätte, 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 so dass has_to_string<T>::value ist true dann und nur dann, wenn T hat eine Methode .toString Das kann in diesem Zusammenhang mit 0 Argumenten aufgerufen werden.

Als nächstes würde ich Tag -Versand 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>{} );
}

Das ist tendenziell besser gewartet als komplexe Sfinae -Ausdrücke.

Sie können diese Eigenschaften mit einem Makro schreiben, wenn Sie es viel tun, aber sie sind relativ einfach (jeweils ein paar Zeilen), also ist es vielleicht nicht wert:

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

Das Erstellen eines Makros erstellen Sie ein Makro MAKE_CODE_TRAIT. Sie übergeben es den Namen des gewünschten Merkmals und einen Code, der den Typ testen kann T. Daher:

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 "Expression Sfinae" bezeichnet, und ihr Compiler von 2013 scheitert ziemlich hart.

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 eine Inline -Kompilierung bedingter Zweig, die viele C ++ - Funktionen missbraucht. Dies lohnt sich wahrscheinlich nicht, da der Nutzen (von Code inline) die Kosten nicht wert ist (neben niemandem, der versteht, wie er funktioniert), aber die Existenz der oben genannten Lösung kann von Interesse sein.

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

Überprüfen Sie das Mitglied x in einer bestimmten Klasse. Könnte VaR, Func, Klasse, Union oder Enum sein:

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

Überprüfen Sie die Mitgliederfunktion 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;

Überprüfen Sie die Mitgliedervariable x:

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

Überprüfen Sie die Mitgliedsklasse x:

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

Überprüfen Sie die Mitgliedervereinigung x:

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

Überprüfen Sie, ob Sie Mitglied des Mitglieds sind x:

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

Überprüfen Sie die Mitgliederfunktion x Unabhängig von der Unterschrift:

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 create_member_func_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)

Die hier von LITB hier vorgestellte Standard -C ++ -Lösung funktioniert nicht wie erwartet, wenn die Methode in einer Basisklasse definiert wird.

Für eine Lösung, die diese Situation bezieht, beziehen Sie sich auf:

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 klug. Ein Problem mit dieser Lösung ist jedoch, dass Compiler -Fehler, wenn der getestete Typ eine Basisklasse verwendet werden kann (z. B. primitive Typen), nicht verwendet werden kann (z.

In Visual Studio bemerkte ich, dass bei der Arbeit mit Methoden mit keinem Argumente ein zusätzliches Paar von redundant () in die Argments eingesetzt werden muss, um () in die Größe des Ausdrucks zu leiten.

MSVC hat die __if_exists und __if_not_exists Schlüsselwörter (Dokument). Zusammen mit dem Typ-of-Sfinae-Ansatz von Nicola konnte ich einen Scheck für GCC und MSVC erstellen, wie das OP suchte.

Aktualisieren: Quelle kann gefunden werden Hier

Ich habe eine Antwort darauf in einem anderen Thread geschrieben, der (im Gegensatz zu den oben genannten Lösungen) auch die geerbten Mitgliederfunktionen überprüft:

Sfinae nach ererbten Mitgliedsfunktionen suchen

Hier sind einige Beispiele aus dieser Lösung:

Beispiel 1:

Wir suchen nach einem Mitglied mit der folgenden 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 Constness der Methode überprüft und auch mit primitiven Typen funktioniert. (Ich meine has_const_begin<int>::value ist falsch und verursacht keinen Kompilierungsfehler.)

Beispiel 2

Jetzt suchen wir nach der Signatur: 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 konstruktibel sein oder ein besonderes Konzept erfüllen muss. Die Technik funktioniert auch mit Vorlagenmitgliedern.

Ich warte gespannt auf dies.

Nun war das ein Hübsch Kleines Puzzle - Tolle Frage!

Hier ist eine Alternative zu Nicola Bonellis Lösung Das beruht nicht auf den nicht standardmäßigen typeof Operator.

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

Der längere Kommentarblock enthält die Details, wie er funktioniert (oder soll funktionieren). Ich bin mir nicht sicher, welches Verhalten Standards konform ist - ich würde Kommentare dazu begrüßen.


Update - 7. November 2008:

Es sieht so aus, als ob dieser Code syntaktisch korrekt ist, das Verhalten, das MSVC und Comeau C ++ zeigen, dem Standard nicht folgt (dank Leon Timmmans und Litb dafür, mich in die richtige Richtung zu zeigen). Der C ++ 03 -Standard besagt Folgendes:

14.6.2 Abhängige Namen [temp.dep

Absatz 3

In der Definition einer Klassenvorlage oder eines Mitglieds einer Klassenvorlage, wenn eine Basisklasse der Klassenvorlage von einem Vorlage-Parameter abhängt, wird der Basisklassenbereich bei uneingeschränkter Name auch nicht zum Zeitpunkt der Definition der Klasse untersucht Vorlage oder Mitglied oder während einer Instanziierung der Klassenvorlage oder des Mitglieds.

Es sieht also so aus, wenn MSVC oder Comeau die berücksichtigen toString() Mitgliedsfunktion von T Ausführen von Namensuche auf der Anrufseite in doToString() Wenn die Vorlage instanziiert ist, ist dies 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 der Nichtmitglied toString() Funktion ist an den Anruf gebunden.

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


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

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

Das folgende Makro kann verwendet werden, um zu prüfen, ob eine bestimmte Klasse eine bestimmte TypedeFef 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 Mitgliedsfunktion hat oder nicht mit einer bestimmten Anzahl von Argumenten.

/** 
 * @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 oben genannten 2 Makros verwenden, um die Schecks für Has_typedef und Has_MEM_Func 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;
}

Ein Beispiel unter Verwendung von Sfinae und Template Partial Specialization durch Schreiben a Has_foo Konzeptprü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");

Seltsam niemand schlug den folgenden netten 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 es eine Klasse ist. Es scheint, dass Unklarheit bei der Suche von Foo ein Substitutionsversagen ist. Ich habe es auf GCC zum Laufen gebracht, nicht sicher, ob es Standard ist.

Die generische Vorlage, die zur Überprüfung verwendet werden kann, ob eine "Funktion" durch den 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 überprüft, ob es eine Methode gibt foo Das ist mit der Signatur kompatibel double(const char*)

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

Beispiele

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

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

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

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

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

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

    return 0;
}

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

Hier gibt es viele Antworten, aber ich habe versagt, eine Version zu finden, die ausgeführt wird real Auflösungsreihenfolge für Methoden, obwohl keine der neueren C ++ - Funktionen verwendet werden (nur C ++ 98 -Funktionen).
HINWEIS: Diese Version wird getestet und arbeitet mit VC ++ 2013, G ++ 5.2.0 und dem Onlinline -Compiler.

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

template<typename T> T declval(void);

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

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

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


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

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

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

Keine Quelle, als ich mich selbst darauf einfiel.

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

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

Hier ist meine Version, die alle möglichen Überladungen der Mitgliederfunktionen mit willkürlicher Arity übernimmt, einschließlich Vorlagenelementfunktionen, möglicherweise mit Standardargumenten. Es unterscheidet 3 gegenseitig ausschließende Szenarien, wenn ein Mitgliedsfunktionsaufruf an einen Klassentyp mit gegebenen Arg-Typen getätigt wird: (1) gültig oder (2) mehrdeutig oder (3) nicht lebensfähig. Beispiel Verwendung:

#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 Code, der in C ++ 11 geschrieben wurde. Sie können ihn jedoch (mit geringfügigen Optimierungen) auf Nicht-C ++-11 problemlos portieren, die TypeOF-Erweiterungen (z. B. GCC) haben. Sie können das Has_Mem -Makro durch Ihren eigenen 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

Sie können die gesamte Metaprogrammierung in C ++ 14 überspringen und diese einfach verwenden fit::conditional von dem Fit Bibliothek:

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 generische Lambdas nicht 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()
);

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>* = nullptr aktiviert die Funktion, die extra dauert int Argument, das eine Priorität gegenüber der Funktion hat, die nimmt long wenn gerufen mit 0.

Sie können das gleiche Prinzip für die Funktionen verwenden, die zurückgegeben werden true Wenn 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
}

Ich hatte ein ähnliches Problem:

Eine Vorlagenklasse, die aus wenigen Basisklassen abgeleitet werden kann, einige, die ein bestimmtes Mitglied haben, und andere, die dies nicht tun.

Ich habe es ähnlich wie die Antwort "typeof" (Nicola Bonellis) gelöst, aber mit DeclType, damit es auf MSVs korrekt und läuft korrekt:

#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;
}
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!";
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top