it-swarm.com.de

Ist es möglich, eine Vorlage zu schreiben, um die Existenz einer Funktion zu prüfen?

Ist es möglich, eine Vorlage zu schreiben, die das Verhalten ändert, je nachdem, ob eine bestimmte Memberfunktion für eine 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";
}

Wenn also class TtoString() definiert ist, verwendet es es. sonst nicht. Der magische Teil, den ich nicht wissen kann, ist der Teil "FUNCTION_EXISTS".

425
andy

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;
    typedef long two;

    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 laufen. 

295
Nicola Bonelli

Diese Frage ist alt, aber mit C++ 11 haben wir einen neuen Weg gefunden, um das Vorhandensein einer Funktion zu überprüfen (oder das Vorhandensein eines Nicht-Typ-Mitglieds wirklich), 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 benutze ich expression SFINAE , um die serialize(_imp)-Funktionen von der Überladungsauflösung auszuschließen, wenn der erste Ausdruck in decltype nicht gültig ist (dh die Funktion existiert nicht).

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

Das 0-Argument wird verwendet, um die os << obj-Überladung zu bevorzugen, wenn beide verfügbar sind (das Literal 0 ist vom Typ int und daher ist die erste Überladung eine bessere Übereinstimmung).


Nun möchten Sie wahrscheinlich, dass ein Merkmal prüft, ob eine Funktion vorhanden ist. Zum Glück ist das leicht zu schreiben. Beachten Sie jedoch, dass Sie für jeden anderen Funktionsnamen ein Merkmal yourself 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 Erklärungen. Erstens ist sfinae_true ein Helfertyp, der im Wesentlichen dem Schreiben von decltype(void(std::declval<T>().stream(a0)), std::true_type{}) gleichkommt. Der Vorteil ist einfach, dass es kürzer ist.
Als nächstes erbt die struct has_stream : decltype(...) am Ende entweder von std::true_type oder std::false_type, je nachdem, ob die decltype-Überprüfung in test_stream fehlschlägt oder nicht.
Zuletzt gibt Ihnen std::declval einen "Wert" des Typs, den Sie übergeben, ohne dass Sie wissen müssen, wie Sie ihn erstellen können. Beachten Sie, dass dies nur in einem nicht ausgewerteten Kontext möglich ist, z. B. decltype, sizeof und anderen.


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

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

Die Parameter int und long sind aus demselben Grund immer noch vorhanden. Der Arrayzeiger wird verwendet, um einen Kontext bereitzustellen, in dem sizeof verwendet werden kann.

246
Xeo

C++ erlaubt die Verwendung von SFINAE (beachten Sie, dass dies mit C++ 11-Funktionen einfacher ist, da es erweiterte SFINAE-Funktionen für fast beliebige Ausdrücke unterstützt. :

#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, bewirkt SFINAE, dass die Vorlage ignoriert wird. 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 die Funktion toString in diesem Zweig nicht einfach aufrufen können. Da der Compiler in beiden Zweigen die Gültigkeit überprüft, schlägt dies für Fälle fehl, in denen die Funktion nicht existiert. Eine Möglichkeit ist, SFINAE erneut 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ß damit. Der Vorteil ist, dass es auch für überladene Memberfunktionen und auch für const-Memberfunktionen funktioniert (denken Sie daran, std::string(T::*)() const als Member-Funktionszeigertyp zu verwenden!). 

155

Obwohl diese Frage zwei Jahre alt ist, wage ich meine Antwort hinzuzufügen. Hoffentlich wird die bisher unbestreitbar hervorragende Lösung geklärt. Ich nahm die sehr hilfreichen Antworten von Nicola Bonelli und Johannes Schaub und führte sie in eine Lösung ein, die IMHO lesbarer und übersichtlicher ist und nicht die Erweiterung typeof 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 .. Die Anerkennung geht hauptsächlich an Nicola Bonelli und Johannes Schaub, also geben Sie ihnen eine Stimme, wenn meine Antwort Ihnen hilft :)

52
FireAphis

Erkennungs-Toolkit

N4502 schlägt eine Erkennung vor, die in die C++ 17-Standardbibliothek aufgenommen werden soll, um das Problem auf elegante Weise zu lösen. Darüber hinaus wurde es gerade in die Bibliotheksgrundlagen TS v2 aufgenommen. Es führt einige Metafunktionen ein, einschließlich std::is_detected , mit deren Hilfe einfach Typ- oder Funktionserkennungs-Metafunktionen oben darauf 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 leicht kopieren können, wenn Sie sie wirklich benötigen. Es spielt Nizza 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 weiteres etwas idiomatisches Toolkit, um eine solche Überprüfung durchzuführen - auch wenn dies weniger elegant ist - ist Boost.TTI , eingeführt in Boost 1.54.0. Für Ihr Beispiel müssten Sie das Makro BOOST_TTI_HAS_MEMBER_FUNCTION verwenden. 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 bool verwenden, um eine SFINAE-Prüfung zu erstellen.

Erklärung

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

Alternativ kann has_member_function_toString einen Elementfunktionszeiger als Vorlagenparameter verwenden. Daher ist es möglich, has_member_function_toString<T, std::string>::value durch has_member_function_toString<std::string T::* ()>::value zu ersetzen.

45
Morwenn

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;
}
28
Konrad Rudolph

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 nicht getestet). 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";
}
28
Aaron McDaid

Nun, diese Frage hat schon eine lange Liste von Antworten, aber ich möchte den Kommentar von Morwenn hervorheben: Es gibt einen Vorschlag für C++ 17, der die Sache 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 einen Header.

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

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

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

dann gibt es den variablen Teil, in dem Sie angeben, wonach Sie suchen (ein Typ, ein Elementtyp, eine Funktion, eine Elementfunktion usw.). Im Falle 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 aufwendigere 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>;

Verglichen mit den anderen oben beschriebenen Implementierungen ist diese Implementierung ziemlich einfach: Ein reduzierter Satz von Tools (void_t und detect) genügt, ohne dass haarige Makros benötigt werden. Außerdem wurde berichtet (siehe N4502 ), dass es messbar effizienter ist (Kompilierzeit und Compilerspeicherverbrauch) als bei früheren Ansätzen.

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, wodurch void_t nicht wie erwartet funktionierte. Yakk hat bereits die Problemumgehung bereitgestellt: Verwenden Sie die folgende Definition von void_t ( void_t in der Parameterliste funktioniert, aber nicht als Rückgabetyp ):

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

Dies ist eine C++ 11-Lösung für das allgemeine Problem, wenn "Wenn ich X mache, 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 {};

Ermitteln Sie has_to_string so, dass has_to_string<T>::valuetrue ist, und zwar nur dann, wenn T über eine Methode .toString verfügt, die in diesem Kontext mit 0 Argumenten aufgerufen werden kann.

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

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

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

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

was oben beschrieben ist, erstellt ein Makro MAKE_CODE_TRAIT. Sie übergeben den Namen der gewünschten Eigenschaft und einen Code, der den Typ T testen kann. Somit:

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

erstellt die oben genannte Merkmalklasse.

Abgesehen davon ist die obige Technik Teil dessen, was MS "Ausdruck SFINAE" nennt, und ihr 2013er Compiler fällt ziemlich schwer aus.

Beachten Sie, dass in C++ 1y 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 Zweig für die Inline-Kompilierung, der viele C++ - Funktionen missbraucht. Dies lohnt sich wahrscheinlich nicht, da der Vorteil (der Code ist inline) den Preis nicht wert ist (so gut wie niemand versteht, wie er funktioniert), aber die Existenz der obigen Lösung kann von Interesse sein.

Hier sind einige Verwendungsausschnitte: * Der Mut zu all dem ist weiter unten

Suche nach Member x in einer gegebenen 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 Member-Funktion void x() prüfen:

//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 Membervariable x prüfen:

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

Auf Mitgliedsklasse x prüfen:

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

Auf Mitgliedsvereinigung prüfen x:

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

Auf Mitgliederaufzählung prüfen x:

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

Suche nach einer beliebigen Memberfunktion x unabhängig von der Signatur:

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;

OR

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)
8
Brett Rossier

Das war jetzt ein nettes kleines Rätsel - tolle Frage!

Hier ist eine Alternative zu Nicola Bonellis Lösung , die nicht auf dem Nicht-Standard-Operator typeof basiert.

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

Der längere Kommentarblock enthält die Details zur Funktionsweise (oder soll funktionieren). 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:

Während dieser Code syntaktisch korrekt ist, folgt das Verhalten, das MSVC und Comeau C++ zeigen, nicht dem Standard (danke an Leon Timmermans und litb , um mich in die richtige Richtung zu weisen Richtung). Der C++ 03-Standard besagt Folgendes:

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 Gültigkeitsbereich der Basisklasse während der Suche nach nicht qualifizierten Namen auch am Definitionspunkt der Klasse nicht geprüft template oder member oder während einer Instanziierung der Klassenvorlage oder des Members.

Wenn also MSVC oder Comeau die Mitgliedsfunktion toString() von T in Betracht ziehen, die beim Instanziieren der Vorlage eine Namenssuche an der Aufrufstelle in doToString() durchführt, ist dies (obwohl) falsch Es ist eigentlich das Verhalten, das ich in diesem Fall gesucht habe.

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

Ratten - Ich dachte, ich hätte vielleicht 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;
}
7
Michael Burr

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

SFINAE zur Suche nach vererbten Memberfunktionen

Hier einige Beispiele aus dieser Lösung:

Beispiel 1:

Wir prüfen nach einem Mitglied mit der folgenden Signatur: 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>::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 konfigurierbar sein muss oder einem speziellen Konzept entsprechen muss. Die Technik funktioniert auch mit Vorlagenmitgliedern.

Ich warte gespannt auf diesbezügliche Meinungen.

6
kispaljr

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

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

In russischer Sprache: http://www.rsdn.ru/forum/message/2759773.1.aspx

Deutsche Ü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 bei dieser Lösung ist jedoch, dass Compiler-Fehler ausgegeben werden, wenn der getestete Typ nicht als Basisklasse verwendet werden kann (z. B. primitive Typen).

In Visual Studio ist mir aufgefallen, dass bei der Arbeit mit einer Methode ohne Argumente ein zusätzliches Paar redundant () um die Argumente eingefügt werden muss, um () in den sizeof-Ausdruck abzuleiten.

6
Roshan

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

Update: Quelle kann gefunden werden Hier

5
nob

Ich habe die in https://stackoverflow.com/a/264088/2712152 zur Verfügung gestellte Lösung geändert, um sie etwas allgemeiner zu machen. Da es keine der neuen C++ 11-Funktionen verwendet, können wir es 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.

Das folgende Makro kann verwendet werden, um zu überprüfen, ob eine bestimmte Klasse einen bestimmten Typ 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 Member-Funktion 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;
}
4
Shubham

Die generische Vorlage, die verwendet werden kann, um zu prüfen, ob ein Typ von dem 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 foo gibt, die mit der Signatur double(const char*) kompatibel ist.

// 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

3
anton_rh

Seltsamerweise schlug niemand den folgenden schönen Trick vor, den ich einmal auf dieser Site gesehen hatte:

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

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

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

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

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

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

3
Alexandre C.

Ein Beispiel mit SFINAE und der partiellen Spezialisierung von Schablonen, indem Sie eine Has_foo-Konzeptüberprüfung schreiben: 

#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");
3
Paul Belanger

Hier gibt es viele Antworten, aber ich konnte nicht nach einer Version suchen, die die Reihenfolge der real -Methoden bestimmt und keine der neueren C++ - Funktionen verwendet (nur mit C++ 98-Funktionen).
Hinweis: Diese Version wurde getestet und arbeitet mit vc ++ 2013, g ++ 5.2.0 und dem Online-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 Überprüfung des Rückgabetyps und Vc ++ 2010-Problemumgehung): http://cpp.sh/5b2vs

Keine Quelle, da ich selbst darauf gekommen bin.

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 ausfällt.
.__ Eine häufig verwendete Problemumgehung besteht darin, die "Typedef" im Makro durch "Extern" zu ersetzen.

2
user3296587

Hier ist meine Version, die alle möglichen Überladungen der Member-Funktion mit beliebiger Berechtigung behandelt, einschließlich Template-Member-Funktionen, möglicherweise mit Standardargumenten. Es unterscheidet drei sich gegenseitig ausschließende Szenarien, wenn ein Member-Funktionsaufruf für einen Klassentyp mit den angegebenen Arg-Typen ausgeführt wird: (1) gültig oder (2) mehrdeutig oder (3) nicht realisierbar. Verwendungsbeispiel:

#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 kannst du es so benutzen:

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 ist. Sie können ihn jedoch (mit geringfügigen Änderungen) leicht auf Nicht-C++ 11 portieren, das Typ-Erweiterungen (z. B. gcc) hat. 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
Hui

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

Sie können die gesamte Metaprogrammierung in C++ 14 überspringen und dies einfach mit fit::conditional aus 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 von den Lambdas aus 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
Paul Fultz II

Ich hatte ein ähnliches Problem:

Eine Vorlagenklasse, die von wenigen Basisklassen abgeleitet werden kann, von denen einige einen bestimmten Member haben und andere nicht.

Ich habe es ähnlich gelöst wie die "typeof" (Nicola Bonelli) Antwort, aber mit decltype so kompiliert und läuft korrekt auf MSVS:

#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
Yigal Eilam
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!";
}
0
Abhishek

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 ein zusätzliches int-Argument benötigt, das Vorrang vor der Funktion hat, die long beim Aufruf mit 0 verwendet.

Sie können dasselbe Prinzip für die Funktionen verwenden, die true zurückgeben, 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
}
0
tereshkd