semplici - esercizi svolti di programmazione c++




È possibile scrivere un modello per verificare l'esistenza di una funzione? (16)

È possibile scrivere un modello che modifica il comportamento a seconda che una determinata funzione membro sia definita in una classe?

Ecco un semplice esempio di ciò che vorrei scrivere:

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

Quindi, se la class T ha toString() definita, allora la usa; altrimenti, non è così. La parte magica che non so come fare è la parte "FUNCTION_EXISTS".


Toolkit di rilevamento

N4502 propone un rilevamento per l'inclusione nella libreria standard C ++ 17 che può risolvere il problema in modo un po 'elegante. Inoltre, è stato appena accettato nei fondamenti della libreria TS v2. Introduce alcune metafunzioni, incluso std::is_detected che può essere usato per scrivere facilmente metafunzioni di rilevamento di tipo o funzione in cima ad esso. Ecco come potresti usarlo:

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

Si noti che l'esempio sopra non è stato verificato. Il toolkit di rilevamento non è ancora disponibile nelle librerie standard ma la proposta contiene un'implementazione completa che puoi facilmente copiare se ne hai davvero bisogno. Funziona bene con la funzione 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";
}

Boost.TTI

Un altro toolkit un po 'idiomatico per eseguire un tale controllo - anche se meno elegante - è Boost.TTI , introdotto in Boost 1.54.0. Per il tuo esempio, dovresti utilizzare la macro BOOST_TTI_HAS_MEMBER_FUNCTION . Ecco come potresti usarlo:

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

Quindi, puoi usare il bool per creare un controllo SFINAE.

Spiegazione

La macro BOOST_TTI_HAS_MEMBER_FUNCTION genera la metafunzione has_member_function_toString che considera il tipo controllato come il suo primo parametro di modello. Il secondo parametro template corrisponde al tipo restituito della funzione membro e i seguenti parametri corrispondono ai tipi dei parametri della funzione. Il value membro contiene true se la classe T ha una funzione membro std::string toString() .

In alternativa, has_member_function_toString può prendere un puntatore a funzione membro come parametro del modello. Pertanto, è possibile sostituire has_member_function_toString<T, std::string>::value da has_member_function_toString<std::string T::* ()>::value .


Anche se questa domanda ha due anni, avrò il coraggio di aggiungere la mia risposta. Spero che chiarisca la precedente, indiscutibilmente eccellente, soluzione. Ho preso le risposte molto utili di Nicola Bonelli e Johannes Schaub e le ho unite in una soluzione che è, IMHO, più leggibile, chiara e non richiede il typeof estensione:

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

L'ho controllato con gcc 4.1.2. Il merito va principalmente a Nicola Bonelli e Johannes Schaub, quindi dai un voto se la mia risposta ti aiuta :)


C ++ consente di utilizzare SFINAE per questo (si noti che con le caratteristiche del C ++ 11 questo è più semplice perché supporta SFINAE estesa su espressioni quasi arbitrarie - il sotto è stato creato per funzionare con i comuni compilatori 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);     \
    }

il modello e la macro sopra cercano di creare un'istanza di un modello, dandogli un tipo di puntatore a funzione membro e il puntatore della funzione membro effettivo. Se i tipi non si adattano, SFINAE fa ignorare il modello. Uso come questo:

HAS_MEM_FUNC(toString, has_to_string);

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

Ma nota che non puoi semplicemente chiamare quella funzione toString in quel ramo if. dal momento che il compilatore verificherà la validità in entrambi i rami, ciò fallirà nei casi in cui la funzione non esiste. Un modo è usare SFINAE ancora una volta (enable_if può essere ottenuto anche da boost):

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

Divertiti a usarlo. Il vantaggio è che funziona anche con funzioni membro sovraccariche e anche con funzioni membro const (ricordate di usare const std::string(T::*)() const come tipo di puntatore funzione membro quindi!).


Ecco alcuni frammenti di utilizzo: * Il coraggio di tutto questo è più basso

Controlla il membro x in una determinata classe. Potrebbe essere var, func, class, union o enum:

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

Controlla la funzione membro 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;

Controlla la variabile membro x :

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

Verifica la classe membro x :

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

Verifica l'unione dei membri x :

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

Controlla l'enum membro x :

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

Verifica la presenza di qualsiasi funzione membro x indipendentemente dalla firma:

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;

O

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;

Dettagli e core:

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

Macro (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)

Questa è una soluzione C ++ 11 per il problema generale se "Se facessi X, sarebbe compilato?"

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

Trait has_to_string tale che has_to_string<T>::value è true se e solo se T ha un metodo .toString che può essere invocato con 0 argomenti in questo contesto.

Successivamente, utilizzerei l'invio di tag:

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

che tende ad essere più manutenibile rispetto alle espressioni complesse di SFINAE.

Puoi scrivere questi tratti con una macro se ti trovi a farlo molto, ma sono relativamente semplici (poche righe ciascuno) quindi forse non ne vale la pena:

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

quello che fa sopra è creare una macro MAKE_CODE_TRAIT . Si passa il nome della caratteristica che si desidera e un codice che può verificare il tipo T Così:

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

crea la classe dei tratti sopra.

Per inciso, la tecnica di cui sopra fa parte di ciò che MS chiama "espressione SFINAE", e il loro compilatore del 2013 non funziona abbastanza.

Nota che in C ++ 1y è possibile la seguente sintassi:

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

che è un ramo condizionale della compilazione inline che abusa di molte funzionalità C ++. Ciò probabilmente non vale la pena, dato che il vantaggio (del codice inline) non vale il costo (di chi non capisce come funziona), ma l'esistenza di quella soluzione potrebbe essere di interesse.


Questa domanda è vecchia, ma con C ++ 11 abbiamo un nuovo modo di verificare l'esistenza di funzioni (o l'esistenza di qualsiasi membro non di tipo, in realtà), affidandoci nuovamente a SFINAE:

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

Ora su alcune spiegazioni. Per prima cosa, uso l' espressione SFINAE per escludere le funzioni serialize(_imp) dalla risoluzione di overload, se la prima espressione all'interno di decltype non è valida (ovvero la funzione non esiste).

Il void() è usato per rendere void() il tipo di ritorno di tutte quelle funzioni.

L'argomento 0 è usato per preferire il sovraccarico di os << obj se entrambi sono disponibili (letteralmente 0 è di tipo int e in quanto tale il primo overload è una corrispondenza migliore).

Ora, probabilmente vuoi un tratto per verificare se esiste una funzione. Fortunatamente, è facile da scrivere. Nota, però, che devi scrivere un tratto tu stesso per ogni nome di funzione diverso che potresti desiderare.

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

Esempio dal vivo

E avanti alle spiegazioni. Innanzitutto, sfinae_true è un tipo helper e sostanzialmente equivale a scrivere decltype(void(std::declval<T>().stream(a0)), std::true_type{}) . Il vantaggio è semplicemente che è più breve.
Successivamente, struct has_stream : decltype(...) eredita da std::true_type o std::false_type alla fine, a seconda se il check test_stream in test_stream fallisce o meno.
Infine, std::declval ti dà un "valore" di qualunque tipo tu passi, senza che tu abbia bisogno di sapere come puoi costruirlo. Si noti che questo è possibile solo all'interno di un contesto non valutato, come decltype , sizeof e altri.

Si noti che decltype non è necessariamente necessario, in quanto sizeof (e tutti i contesti non valutati) ha ottenuto tale miglioramento. È solo che decltype offre già un tipo e in quanto tale è solo più pulito. Ecco una versione sizeof di uno dei sovraccarichi:

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

I parametri int e long sono ancora lì per lo stesso motivo. Il puntatore dell'array viene utilizzato per fornire un contesto in cui può essere utilizzato sizeof .


Sì, con SFINAE puoi verificare se una determinata classe fornisce un determinato metodo. Ecco il codice funzionante:

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

L'ho appena testato con Linux e gcc 4.1 / 4.3. Non so se è portatile ad altre piattaforme che eseguono compilatori diversi.


Una soluzione semplice per C ++ 11:

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

Aggiornamento, 3 anni dopo: (e questo non è stato verificato). Per testare l'esistenza, penso che funzionerà:

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

Ecco un esempio del codice funzionante.

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>* = nullptrabiliterà la funzione che accetta un intargomento extra che ha una priorità sulla funzione che prende longquando viene chiamato con 0.

È possibile utilizzare lo stesso principio per le funzioni che restituisce truese la funzione è implementata.

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
}

Ho scritto una risposta a questo in un altro thread che (diversamente dalle soluzioni sopra) controlla anche le funzioni membro ereditate:

SFINAE per verificare le funzioni dei membri ereditati

Ecco alcuni esempi da quella soluzione:

Esempio 1:

Stiamo verificando la presenza di un membro con la seguente firma: 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));
};

Si noti che controlla anche la costanza del metodo e funziona anche con i tipi primitivi. (Voglio dire has_const_begin<int>::valueè falso e non causa un errore in fase di compilazione.)

Esempio 2

Ora stiamo cercando la firma: 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));
};

Si prega di notare che MyClass non deve essere predefinito costruibile o per soddisfare qualsiasi concetto speciale. La tecnica funziona anche con i membri del modello.

Sto aspettando con impazienza opinioni su questo.


Strano nessuno ha suggerito il seguente bel trucco che ho visto una volta su questo stesso sito:

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

Devi assicurarti che T sia una classe. Sembra che l'ambiguità nella ricerca di pippo sia un fallimento della sostituzione. L'ho fatto funzionare su gcc, non sono sicuro che sia standard.


Ci sono molte risposte qui, ma non sono riuscito a trovare una versione che esegua l' ordinamento di risoluzione del metodo reale , pur non utilizzando nessuna delle funzionalità c ++ più recenti (solo utilizzando le funzionalità di c ++ 98).
Nota: questa versione è testata e funziona con vc ++ 2013, g ++ 5.2.0 e il compilatore onlline.

Quindi mi è venuta in mente una versione, che utilizza solo 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 dal vivo (con controllo dei tipi di reso esteso e soluzione alternativa a vc ++ 2010): http://cpp.sh/5b2vs

Nessuna fonte, come l'ho inventata io stesso.

Quando esegui la demo Live sul compilatore g ++, tieni presente che le dimensioni di array pari a 0 sono consentite, il che significa che il static_assert utilizzato non attiverà un errore del compilatore, anche quando fallisce.
Un work-around comunemente usato è quello di sostituire "typedef" nella macro con "extern".


Ho modificato la soluzione fornita in https://.com/a/264088/2712152 per renderla un po 'più generica. Inoltre, dal momento che non utilizza nessuna delle nuove funzionalità di C ++ 11, possiamo usarlo con i vecchi compilatori e dovrebbe funzionare anche con msvc. Ma i compilatori dovrebbero consentire a C99 di usarlo poiché utilizza macro variadiche.

La seguente macro può essere usata per verificare se una particolare classe ha un typedef particolare o no.

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

La seguente macro può essere utilizzata per verificare se una determinata classe ha una funzione membro specifica o meno con un dato numero di argomenti.

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

Possiamo usare le 2 macro precedenti per eseguire i controlli per has_typedef e has_mem_func come:

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

MSVC ha le parole chiave __if_exists e __if_not_exists ( Doc ). Insieme all'approccio typeof-SFINAE di Nicola ho potuto creare un controllo per GCC e MSVC come l'OP cercato.

Aggiornamento: fonte può essere trovato Here


Puoi saltare tutta la metaprogrammazione in C ++ 14, e scrivilo usando fit::conditionaldalla libreria 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);
}

Puoi anche creare la funzione direttamente dal 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"; }
);

Tuttavia, se si utilizza un compilatore che non supporta lambda generico, sarà necessario scrivere oggetti funzione separati:

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

Un esempio che utilizza SFINAE e specializzazione parziale del modello, scrivendo un Has_foocontrollo concettuale:

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






sfinae