Template check for the existing of a class member function?

Czy jest możliwe napisanie szablonu, który zmienia zachowanie w zależności od tego, czy określona funkcja członkowska jest zdefiniowana na klasie?

Oto prosty przykład tego, co chciałbym napisać:

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

Więc jeśli class T ma zdefiniowaną toString(), to używa jej; w przeciwnym razie nie. magiczną częścią, której Nie wiem, jak to zrobić, jest część "FUNCTION_EXISTS".

Author: einpoklum, 2008-11-02

30 answers

Tak, dzięki SFINAE możesz sprawdzić, czy dana klasa zapewnia określoną metodę. Oto kod roboczy:

#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( decltype(&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;
}

Właśnie Przetestowałem go z Linuksem i gcc 4.1/4.3. Nie wiem czy jest przenośny na inne platformy z różnymi kompilatorami.

 340
Author: Nicola Bonelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-10 05:54:17

To pytanie jest stare, ale z C++11 mamy nowy sposób sprawdzania istnienia funkcji (lub istnienia dowolnego elementu innego typu, naprawdę), opierając się na SFINAE ponownie:]}

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

Teraz kilka wyjaśnień. Po pierwsze, używam wyrażenia SFINAE , aby wykluczyć funkcje serialize(_imp) z rozdzielczości przeciążenia, jeśli pierwsze wyrażenie wewnątrz decltype nie jest poprawne (aka, funkcja nie istnieje).

void() jest używany do zwracania typu wszystkich tych funkcji void.

Argument 0 jest używany do preferowania przeciążenia os << obj, jeśli oba są dostępne (literał 0 jest typu int i jako taki pierwsze przeciążenie jest lepszym dopasowaniem).


Teraz, prawdopodobnie chcesz cecha sprawdzić, czy funkcja istnieje. Na szczęście łatwo to napisać. Zauważ jednak, że musisz napisać cechę samodzielnie dla każdej innej nazwy funkcji, którą możesz chcieć.

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

Przykład na żywo.

I do wyjaśnień. Po pierwsze, sfinae_true jest typem pomocniczym i zasadniczo odpowiada temu samemu, co pisanie decltype(void(std::declval<T>().stream(a0)), std::true_type{}). Zaletą jest po prostu to, że jest krótszy.
Następnie struct has_stream : decltype(...) dziedziczy z std::true_type lub std::false_type na końcu, w zależności od tego, czy sprawdzenie decltype w test_stream nie powiedzie się, czy nie.
Na koniec, std::declval daje Ci "wartość" dowolnego typu, który przechodzisz, bez potrzeby wiedzieć, jak możesz ją skonstruować. Zauważ, że jest to możliwe tylko w bezcennym kontekście, takim jak decltype, sizeof oraz i inni


Zauważ, że decltype nie jest koniecznie potrzebne, ponieważ sizeof (i wszystkie niedoceniane konteksty) otrzymały to ulepszenie. Po prostu decltype już dostarcza typ i jako taki jest po prostu czystszy. Oto sizeof wersja jednego z przeciążeń:

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

Parametry int i long nadal istnieją z tego samego powodu. Wskaźnik tablicy służy do zapewnienia kontekstu, w którym można użyć sizeof.

 274
Author: Xeo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-15 16:33:15

C++ pozwala SFINAE być używane do tego celu (zauważ, że z funkcjami C++11 jest to prostsze, ponieważ obsługuje rozszerzone SFINAE na prawie dowolnych wyrażeniach - poniżej został stworzony do pracy ze zwykłymi kompilatorami C++03):

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

Powyższy szablon i makro próbują utworzyć instancję szablonu, nadając mu typ wskaźnika funkcji członka i wskaźnik rzeczywistej funkcji członka. Jeśli typy nie pasują, SFINAE powoduje ignorowanie szablonu. Użycie jak to:

HAS_MEM_FUNC(toString, has_to_string);

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

Ale zauważ, że nie możesz po prostu wywołać tej toString funkcji w gałęzi if. ponieważ kompilator sprawdzi poprawność w obu gałęziach, to w przypadku, gdy funkcja nie istnieje, to się nie powiedzie. Jednym ze sposobów jest ponowne użycie SFINAE (enable_if można również uzyskać z Boosta): {]}

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.";
}
Miłej zabawy. Jego zaletą jest to, że działa również dla przeciążonych funkcji Członkowskich, a także dla funkcji const (pamiętaj, że używasz std::string(T::*)() const jako wskaźnika funkcji member więc!).
 161
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-11 17:27:23

C++20 - requires wyrażenia

Z C++20 pochodzą koncepcje i różne narzędzia, takie jak requires wyrażenia , które są wbudowanym sposobem sprawdzania istnienia funkcji. Dzięki nim możesz przepisać swoją funkcję optionalToString w następujący sposób:

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-Zestaw Narzędzi Do wykrywania

N4502 proponuje zestaw narzędzi do wykrywania w celu włączenia do biblioteki standardowej C++17, która ostatecznie trafiła do biblioteki fundamentals TS v2. Najprawdopodobniej nigdy nie dostanie się do standard, ponieważ od tego czasu został podporządkowany wyrażeniom requires, ale nadal rozwiązuje problem w nieco elegancki sposób. Zestaw narzędzi wprowadza niektóre metafunkcje, w tym std::is_detected który może być używany do łatwego zapisu metafunkcji wykrywania typu lub funkcji na jego górze. Oto jak możesz go użyć:

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

Zauważ, że powyższy przykład nie jest testowany. Zestaw narzędzi do wykrywania nie jest jeszcze dostępny w bibliotekach standardowych, ale propozycja zawiera pełną implementację które możesz łatwo skopiować, jeśli naprawdę tego potrzebujesz. Gra ładnie z funkcją C++17 if constexpr:

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

C++14-Boost.Hana

Boost.Hana najwyraźniej opiera się na tym konkretnym przykładzie i dostarcza rozwiązanie dla C++14 w swojej dokumentacji, więc zacytuję go bezpośrednio: {]}

[...[ 1] Hana dostarcza is_valid funkcję, którą można łączyć z C++14 generic lambda, aby uzyskać znacznie czystszą implementację tego samego:

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

To pozostawia nas z obiekt function has_toString, który zwraca, czy dane wyrażenie jest poprawne na przekazywanym argumencie. Wynik jest zwracany jako IntegralConstant, więc constexpr-Ness nie jest tutaj problemem, ponieważ wynik funkcji jest reprezentowany jako typ. Teraz, oprócz tego, że jest mniej gadatliwy (to jedna linijka!), intencja jest znacznie jaśniejsza. Inne korzyści to fakt, że has_toString może być przekazywana do algorytmów wyższego rzędu i może być również zdefiniowana w zakresie funkcji, więc nie ma potrzeby zanieczyszczania zakres przestrzeni nazw ze szczegółami implementacji.

Boost.TTI

Kolejnym nieco idiomatycznym zestawem narzędzi do wykonania takiej kontroli - choć mniej eleganckiej - jest Boost.TTI, wprowadzony w Boost 1.54.0. Dla przykładu należy użyć makra BOOST_TTI_HAS_MEMBER_FUNCTION. Oto jak możesz go użyć:

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

Następnie możesz użyć bool, aby utworzyć sprawdzenie SFINAE.

Wyjaśnienie

Makro BOOST_TTI_HAS_MEMBER_FUNCTION generuje metafunkcję has_member_function_toString który przyjmuje typ sprawdzony jako pierwszy parametr szablonu. Drugi parametr szablonu odpowiada typowi zwracanej funkcji pręta, a następujące parametry odpowiadają typom parametrów funkcji. Member value zawiera true, jeśli Klasa T ma funkcję member std::string toString().

Alternatywnie, {[18] } może przyjąć wskaźnik funkcji member jako parametr szablonu. Dlatego możliwe jest zastąpienie has_member_function_toString<T, std::string>::value przez has_member_function_toString<std::string T::* ()>::value.

 118
Author: Morwenn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Chociaż to pytanie ma dwa lata, ośmielę się dodać swoją odpowiedź. Mam nadzieję, że wyjaśni to poprzednie, bezsprzecznie doskonałe rozwiązanie. Wziąłem bardzo pomocne odpowiedzi Nicola Bonelli i Johannes Schaub i połączył je w rozwiązanie, które jest, IMHO, bardziej czytelne, jasne i nie wymaga rozszerzenia typeof:

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

Sprawdziłem to z gcc 4.1.2. Podziękowania należą się głównie Nicoli Bonelli i Johannesowi Schaubowi, więc dajcie im głos, Jeśli moja odpowiedź wam pomoże:)]}

 57
Author: FireAphis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-02 13:39:24

Proste rozwiązanie dla C++11:

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

Update, 3 lata później: (i to jest nieprzetestowane). Aby sprawdzić istnienie, myślę, że to zadziała:

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";
}
 32
Author: Aaron McDaid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-15 18:49:19

Po to są cechy typu. Niestety, muszą być zdefiniowane ręcznie. W Twoim przypadku wyobraź sobie, co następuje:

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;
}
 30
Author: Konrad Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-20 16:32:40

Cóż, to pytanie ma już długą listę odpowiedzi, ale chciałbym podkreślić komentarz Morwenna: jest propozycja dla C++17, która sprawia, że jest to naprawdę dużo prostsze. Zobacz N4502 dla szczegółów, ale jako samodzielny przykład rozważ następujące.

Ta część jest częścią stałą, umieść ją w nagłówku.

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

Następnie jest część zmiennej, w której określasz, czego szukasz(typ, typ członka, funkcja, funkcja członka itp.). W przypadek OP:

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

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

Poniższy przykład, zaczerpnięty z N4502 , pokazuje bardziej rozbudowaną sondę:

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

W porównaniu z innymi implementacjami opisanymi powyżej, ta jest dość prosta: wystarczy zredukowany zestaw narzędzi (void_t i detect), nie ma potrzeby stosowania hairy macros. Poza tym donoszono (zobacz N4502), że jest on znacznie bardziej wydajny (czas kompilacji i zużycie pamięci kompilatora) niż poprzednie podejścia.

Oto NA ŻYWO przykład . Działa dobrze z Clang, ale niestety, wersje GCC przed 5.1 podążały inną interpretacją standardu C++11, co spowodowało, że void_t nie działa zgodnie z oczekiwaniami. Yakk już dostarczył obejście: użyj następującej definicji void_t (void_t w liście parametrów działa, ale nie jako typ zwracany):

#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
 26
Author: akim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:00

Jest to C++11 rozwiązanie ogólnego problemu, jeśli " gdybym zrobił X, to skompilowałby?"

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

Cecha has_to_string taka, że has_to_string<T>::value jest true wtedy i tylko wtedy, gdy T ma metodę .toString, która może być wywołana z 0 argumentami w tym kontekście.

Następnie użyłbym znacznika dispatching:

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

Które wydają się być bardziej łatwe do utrzymania niż złożone wyrażenia SFINAE.

Możesz napisać te cechy za pomocą makra, jeśli znajdziesz się robi to dużo, ale są one stosunkowo proste (a po kilka linijek) więc może nie warto:

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

Powyższe działanie polega na utworzeniu makra MAKE_CODE_TRAIT. Przekazujesz mu nazwę wybranej cechy i kod, który może przetestować typ T. Tak więc:

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

Tworzy powyższą klasę cech.

Na marginesie, powyższa technika jest częścią tego, co MS nazywa "expression SFINAE", a ich kompilator 2013 zawodzi dość mocno.

Zauważ, że w C++1y możliwa jest następująca składnia:

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

Który jest inline kompilacyjny oddział warunkowy wykorzystujący wiele funkcji C++. Robienie tego prawdopodobnie nie jest tego warte, ponieważ korzyść (z tego, że kod jest wbudowany) nie jest warta kosztów( z tego, że nikt nie rozumie, jak to działa), ale istnienie powyższego rozwiązania może być interesujące.

 12
Author: Yakk - Adam Nevraumont,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-09 16:10:24

Oto kilka fragmentów użycia: / Align = "center" bgcolor = "# e0ffe0 " / cesarz Chin / / align = center / ]}

Sprawdź czy członek x w danej klasie. Może to być var, func, class, union lub enum:

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

Sprawdzanie funkcji członka 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;

Sprawdzanie zmiennej członkowskiej x:

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

Sprawdź klasę członkax:

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

Sprawdź związek członkowskix:

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

Sprawdź, czy członek enum x:

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

Sprawdź, czy nie ma żadnej funkcji członka x niezależnie od podpisu:

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;

Lub

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;

Szczegóły i rdzeń:

/*
    - 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;
};

Makra (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)
 10
Author: Brett Rossier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-09 00:58:10

Napisałem na to odpowiedź w innym wątku, który (w przeciwieństwie do powyższych rozwiązań) sprawdza również dziedziczone funkcje Członkowskie:

SFINAE do sprawdzenia dziedzicznych funkcji Członkowskich

Oto przykład z tego rozwiązania:

Przykład1:

Sprawdzamy członka z następującym podpisem: 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));
};

Proszę zauważyć, że sprawdza ona nawet stałość metody i działa również z typami prymitywnymi. (Znaczy has_const_begin<int>::value jest false i nie powoduje błędu w czasie kompilacji.)

Przykład 2

Teraz szukamy podpisu: 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));
};

Proszę zauważyć, że MyClass nie musi być domyślną konstrukcją ani spełniać żadnej specjalnej koncepcji. Technika ta działa również z szablonami.

Z niecierpliwością czekam na opinie na ten temat.
 8
Author: kispaljr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:37

Teraz to było ładne małe puzzle-świetne pytanie!

Oto alternatywa dla rozwiązania Nicoli Bonelli , które nie opiera się na niestandardowym operatorze typeof.

Niestety, nie działa na GCC (MinGW) 3.4.5 lub Digital Mars 8.42 n, ale działa na wszystkich wersjach MSVC (w tym VC6) i na Comeau C++.

Dłuższy blok komentarzy zawiera szczegółowe informacje o tym, jak działa (lub ma działać). Jak mówi, nie jestem pewien, które zachowanie jest standardy zgodne - z chęcią bym to skomentował.


Aktualizacja-7 Lis 2008:

Wygląda na to, że chociaż ten kod jest poprawny składniowo, zachowanie, które wykazują MSVC i Comeau C++, nie jest zgodne ze standardem (dzięki Leon Timmermans i litb za wskazanie mnie we właściwym kierunku). Standard C++03 mówi co następuje:

14.6.2 nazwy zależne [temp.dep]

Paragraf 3

W definicji klasy szablon lub członkiem wzorca klasowego, jeżeli klasa bazowa szablonu klasy zależy od szablonu-parametru, zakres klasy bazowej nie jest badany podczas wyszukiwania nazwy niekwalifikowanej albo w punkcie definicji szablon klasy lub członka lub podczas instancjacja szablonu klasy lub członek.

Wygląda więc tak, gdy MSVC lub Comeau rozważą toString() funkcję członka T wykonującą name lookup w miejscu wywołania w doToString(), gdy szablon jest instancją, to jest nieprawidłowe (mimo, że jest to rzeczywiście zachowanie szukałem w tym przypadku).

Zachowanie GCC i Digital Mars wydaje się być poprawne - w obu przypadkach funkcja non-member toString() jest powiązana z wywołaniem.

Szczury-myślałem, że znalazłem sprytne rozwiązanie, zamiast tego odkryłem kilka błędów kompilatora...


#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
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:37

Standardowe rozwiązanie C++ przedstawione tutaj przez litb nie będzie działać zgodnie z oczekiwaniami, jeśli metoda zostanie zdefiniowana w klasie bazowej.

Aby rozwiązać tę sytuację, zobacz:

Po Rosyjsku : http://www.rsdn.ru/forum/message/2759773.1.aspx

Tłumaczenie na polski: Roman.Perepelitsa : http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

It is insanely sprytne. Jednak jeden problem z tym rozwiązaniem jest to, że daje błędy kompilatora, jeśli testowany typ jest taki, który nie może być użyty jako klasa bazowa (np. typy prymitywne)

W Visual Studio zauważyłem, że jeśli praca z metodą bez argumentów, dodatkowa para zbędnych ( ) musi być wstawiona wokół argentów, aby wydedukować () w wyrażeniu sizeof.

 6
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-02 08:35:41

MSVC posiada słowa kluczowe _ _ if _ exists i _ _ if _ not _ exists (Doc). Wraz z podejściem typeof-SFINAE Nicola mogłem stworzyć czek dla GCC i MSVC, jak szukał OP.

Update: Źródło można znaleźć Tutaj

 6
Author: nob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-30 18:26:49

Przykład wykorzystujący SFINAE i szablon częściowej specjalizacji, pisząc Has_foo sprawdzanie koncepcji:

#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");
 6
Author: Paul Belanger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-18 14:33:40

Zmodyfikowałem roztwór podany w https://stackoverflow.com/a/264088/2712152 żeby było bardziej ogólne. Również ponieważ nie używa żadnej z nowych funkcji C++11 możemy używać go ze starymi kompilatorami i powinniśmy również pracować z msvc. Ale Kompilatory powinny umożliwić C99 użycie tego, ponieważ używa różnych makr.

Następujące makro może być użyte do sprawdzenia, czy dana klasa ma określony typedef, czy nie.

/** 
 * @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); \
   }

Następujące makro może być użyte do sprawdzenia, czy szczególna klasa ma określoną funkcję członka lub nie z dowolną liczbą argumentów.

/** 
 * @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); \
   }

Możemy użyć powyższych 2 makr do wykonania kontroli has_typedef i has_mem_func jako:

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;
}
 5
Author: Shubham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:37

W C++ 20 możesz napisać:

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

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}
 5
Author: Bernd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-04 20:29:27

Dziwne nikt nie zasugerował poniższej fajnej sztuczki, którą widziałem kiedyś na tej właśnie stronie:

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;
};
Musisz się upewnić, że T jest klasą. Wydaje się, że niejednoznaczność w wyszukiwaniu foo jest porażką zastępczą. Zrobiłem to na gcc, nie jestem pewien, czy jest to standard.
 4
Author: Alexandre C.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-23 13:56:49

Ogólny szablon, który może być użyty do sprawdzenia, czy jakaś "funkcja" jest obsługiwana przez typ:

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

Szablon sprawdzający, czy istnieje metoda foo zgodna z podpisem 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*>())));

Przykłady

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

 4
Author: anton_rh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-10 15:02:54

Co powiesz na to rozwiązanie?

#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 { };
 2
Author: user1095108,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-08 19:25:41

Jest tu wiele odpowiedzi, ale nie udało mi się znaleźć wersji, która wykonuje rzeczywistą kolejność rozdzielczości metody, nie używając żadnej z nowszych funkcji c++ (tylko używając funkcji c++98).
Uwaga: Ta wersja jest testowana i działa z VC++2013, g++ 5.2.0 i kompilatorem onlline.

Więc wymyśliłem wersję, która używa tylko sizeof ():

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,"");

Demo na żywo (z rozszerzonym sprawdzaniem typu powrotu i obejściem VC++2010): http://cpp.sh/5b2vs

Brak źródła, jak sam to wymyśliłem.

Podczas uruchamiania Live demo na kompilatorze g++ należy pamiętać, że dozwolone są rozmiary tablic 0, co oznacza, że użyty static_assert nie spowoduje błędu kompilatora, nawet jeśli się nie powiedzie.
Powszechnie stosowanym obejściem jest zastąpienie 'typedef' w makrze 'extern'.

 2
Author: user3296587,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-07 08:41:58

Jeszcze jeden sposób na zrobienie tego w C++17 (zainspirowany przez boost: hana).

Napisz go raz i używaj wiele razy. Nie wymaga klas cech typu SFINAE.

////////////////////////////////////////////
// is_valid implementation
////////////////////////////////////////////

#include <type_traits>

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

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

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

////////////////////////////////////////////
// Example
////////////////////////////////////////////

#include <iostream>
#include <string>

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

struct Example2 {
    int X;
};

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

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

    Example e1;
    Example2 e2;

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

 2
Author: Dmytro Ovdiienko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-02-11 09:00:03

Oto moja wersja, która obsługuje wszystkie możliwe przeciążenia funkcji Członkowskich z dowolną arytmetyką, w tym funkcje członków szablonu, ewentualnie z domyślnymi argumentami. Rozróżnia 3 wzajemnie wykluczające się scenariusze podczas wywoływania funkcji członka do pewnego typu klasy, z podanymi typami arg: (1) valid, lub (2) niejednoznaczne, lub (3) non-viable. Przykład użycia:

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

Teraz możesz go używać tak:

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

Oto kod, napisany w c++11, jednak można go łatwo portować (z drobne poprawki) do innych niż c++11, które mają rozszerzenia typu (np. gcc). Możesz zastąpić makro HAS_MEM swoim własnym.

#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
Author: Hui,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-11 17:28:57

Możesz pominąć wszystkie metaprogramowanie w C++14 i po prostu napisać to za pomocą fit::conditional z biblioteki Fit :

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

Możesz również utworzyć funkcję bezpośrednio z lambda:

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

Jeśli jednak używasz kompilatora, który nie obsługuje ogólnych lambd, będziesz musiał napisać osobne obiekty funkcyjne:

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
Author: Paul Fultz II,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-04 23:50:27

Wiem, że to pytanie ma wiele lat, ale myślę, że byłoby przydatne dla ludzi takich jak ja, aby mieć bardziej kompletną zaktualizowaną odpowiedź, która działa również dla const przeciążonych metod, takich jak std::vector<>::begin.

Bazując na tej odpowiedzi i tej odpowiedzi z mojego pytania, oto bardziej kompletna odpowiedź. Zauważ, że będzie to działać tylko z C++11 i nowszymi.

#include <iostream>
#include <vector>

class EmptyClass{};

template <typename T>
class has_begin
{
    private:
    has_begin() = delete;
    
    struct one { char x[1]; };
    struct two { char x[2]; };

    template <typename C> static one test( decltype(void(std::declval<C &>().begin())) * ) ;
    template <typename C> static two test(...);    

public:
    static constexpr bool value = sizeof(test<T>(0)) == sizeof(one);
};
    
int main(int argc, char *argv[])
{
    std::cout << std::boolalpha;
    std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
    std::cout << "EmptyClass::begin() exists: " << has_begin<EmptyClass>::value << std::endl;
    return 0;
}

Lub krótsza wersja:

#include <iostream>
#include <vector>

class EmptyClass{};

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

template <typename T>
struct has_begin<T, decltype(void(std::declval<T &>().begin()))> : std::true_type {};

int main(int argc, char *argv[])
{
    std::cout << std::boolalpha;
    std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
    std::cout << "EmptyClass exists: " << has_begin<EmptyClass>::value << std::endl;
}

Zauważ, że tutaj należy podać pełne przykładowe wywołanie. Oznacza to, że gdybyśmy zbadali istnienie metody resize, umieścilibyśmy resize(0).

Wyjaśnienie głębokiej magii :

Pierwsza odpowiedź na to pytanie została użyta test( decltype(&C::helloworld) ); jest to jednak problematyczne, gdy metoda, którą testuje, jest niejednoznaczna z powodu przeciążenia, co powoduje, że próba zastąpienia nie powiedzie się.

Do rozwiązania tej dwuznaczności używamy instrukcji void, która może przyjmować dowolne parametry, ponieważ zawsze jest tłumaczona na noop i tym samym niejednoznaczność jest nullifikowana i wywołanie jest ważne tak długo, jak długo istnieje metoda:

has_begin<T, decltype(void(std::declval<T &>().begin()))>

Oto co się dzieje w kolejności: Używamy std::declval<T &>(), aby utworzyć wywoływalną wartość, dla której można wywołać begin. Następnie wartość begin jest przekazywana jako parametr do instrukcji void. Następnie pobieramy Typ tego wyrażenia void za pomocą wbudowanego decltype, aby mogło być użyte jako argument typu szablonu. Jeśli begin nie istnieje, to podstawienie jest nieważne i jak na SFINAE drugie zamiast tego używa się deklaracji.

 1
Author: TommyD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-10 05:26:41

Oto przykład kodu roboczego.

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 włącza funkcję, która pobiera dodatkowy argument int, który ma priorytet nad funkcją, która pobiera long, gdy zostanie wywołana z 0.

Możesz użyć tej samej zasady dla funkcji, które zwracają true Jeśli funkcja jest zaimplementowana.

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
Author: tereshkd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-01 22:29:06

Miałem podobny problem:

Klasa szablonowa, która może pochodzić z kilku klas bazowych, niektóre z nich mają określony element, a inne nie.

Rozwiązałem to podobnie do odpowiedzi" typeof " (Nicola Bonelli), ale z decltype więc kompiluje i działa poprawnie na 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
Author: Yigal Eilam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-31 12:18:11

Moje podejście: aby powszechnie określić, czy coś jest możliwe do wywołania bez tworzenia szczegółowych cech typu dla każdego z nich, lub za pomocą funkcji eksperymentalnych lub długiego kodu:

template<typename Callable, typename... Args, typename = decltype(declval<Callable>()(declval<Args>()...))>
std::true_type isCallableImpl(Callable, Args...) { return {}; }

std::false_type isCallableImpl(...) { return {}; }

template<typename... Args, typename Callable>
constexpr bool isCallable(Callable callable) {
    return decltype(isCallableImpl(callable, declval<Args>()...)){};
}

Użycie:

constexpr auto TO_STRING_TEST = [](auto in) -> decltype(in.toString()) { return {}; };
constexpr bool TO_STRING_WORKS = isCallable<T>(TO_STRING_TEST);
 0
Author: Sean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-28 08:17:13

Prawdopodobnie nie tak dobry jak inne przykłady, ale to jest to, co wymyśliłem dla C++11. To działa na zbieranie przeciążonych metod.

template <typename... Args>
struct Pack {};

#define Proxy(T) ((T &)(*(int *)(nullptr)))

template <typename Class, typename ArgPack, typename = nullptr_t>
struct HasFoo
{
    enum { value = false };
};

template <typename Class, typename... Args>
struct HasFoo<
    Class,
    Pack<Args...>,
    decltype((void)(Proxy(Class).foo(Proxy(Args)...)), nullptr)>
{
    enum { value = true };
};

Przykładowe użycie

struct Object
{
    int foo(int n)         { return n; }
#if SOME_CONDITION
    int foo(int n, char c) { return n + c; }
#endif
};

template <bool has_foo_int_char>
struct Dispatcher;

template <>
struct Dispatcher<false>
{
    template <typename Object>
    static int exec(Object &object, int n, char c)
    {
        return object.foo(n) + c;
    }
};

template <>
struct Dispatcher<true>
{
    template <typename Object>
    static int exec(Object &object, int n, char c)
    {
        return object.foo(n, c);
    }
};

int runExample()
{
    using Args = Pack<int, char>;
    enum { has_overload = HasFoo<Object, Args>::value };
    Object object;
    return Dispatcher<has_overload>::exec(object, 100, 'a');
}
 0
Author: Thomas Eding,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-21 16:33:35
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!";
}
 -1
Author: Abhishek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-25 13:16:47