it-swarm.it

Controlla se una classe ha una funzione membro di una data firma

Sto chiedendo un trucco modello per rilevare se una classe ha una funzione membro specifica di una data firma.

Il problema è simile a quello citato qui http://www.gotw.ca/gotw/071.htm ma non è lo stesso: nell'articolo del libro di Sutter ha risposto alla domanda che una classe DEVE FORNIRE una funzione membro con una firma particolare, altrimenti il ​​programma non verrà compilato. Nel mio problema devo fare qualcosa se una classe ha quella funzione, altrimenti fare "qualcos'altro".

Un problema simile è stato affrontato da boost :: serialization ma non mi piace la soluzione adottata: una funzione modello che richiama per impostazione predefinita una funzione libera (che è necessario definire) con una firma particolare a meno che non si definisca una funzione membro specifica ( nel loro caso "serializzare" che accetta 2 parametri di un determinato tipo) con una firma particolare, altrimenti si verificherà un errore di compilazione. Ciò significa implementare la serializzazione sia intrusiva che non intrusiva.

Non mi piace quella soluzione per due motivi:

  1. Per non essere invadente, è necessario ignorare la funzione globale di "serializzazione" che si trova nello spazio dei nomi boost :: serialization, quindi è NOSTRO CODICE CLIENTE per aprire boost dello spazio dei nomi e serializzazione dello spazio dei nomi!
  2. Lo stack per risolvere quel casino era da 10 a 12 invocazioni di funzioni.

Devo definire un comportamento personalizzato per le classi che non hanno quella funzione membro e le mie entità si trovano all'interno di spazi dei nomi diversi (e non voglio ignorare una funzione globale definita in uno spazio dei nomi mentre sono in un altro)

Puoi darmi un suggerimento per risolvere questo enigma?

121
ugasoft

Non sono sicuro di averti compreso correttamente, ma potresti sfruttare SFINAE per rilevare la presenza della funzione in fase di compilazione. Esempio dal mio codice (verifica se la classe ha la funzione membro size_t used_memory () const).

template<typename T>
struct HasUsedMemoryMethod
{
    template<typename U, size_t (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
    template<typename U> static int Test(...);
    static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};

template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
        // We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
    ReportMemUsage(m, 
        std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}
87
yrp

Ecco una possibile implementazione basata sulle funzionalità di C++ 11. Rileva correttamente la funzione anche se ereditata (diversamente dalla soluzione nella risposta accettata, come osserva Mike Kinghan in la sua risposta ).

La funzione testata da questo frammento si chiama serialize:

#include <type_traits>

// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.

template<typename, typename T>
struct has_serialize {
    static_assert(
        std::integral_constant<T, false>::value,
        "Second template parameter needs to be of function type.");
};

// specialization that does the checking

template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
    template<typename T>
    static constexpr auto check(T*)
    -> typename
        std::is_same<
            decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
            Ret    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        >::type;  // attempt to call it and see if the return type is correct

    template<typename>
    static constexpr std::false_type check(...);

    typedef decltype(check<C>(0)) type;

public:
    static constexpr bool value = type::value;
};

Uso:

struct X {
     int serialize(const std::string&) { return 42; } 
};

struct Y : X {};

std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1
119
jrok

La risposta accettata a questa domanda sull'introspezione della funzione membro di compiletime, sebbene sia giustamente popolare, ha un intoppo che può essere osservato nel seguente programma:

#include <type_traits>
#include <iostream>
#include <memory>

/*  Here we apply the accepted answer's technique to probe for the
    the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
    template<typename U, E (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::operator*>*);
    template<typename U> static int Test(...);
    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};

using namespace std;

/* Here we test the `std::` smart pointer templates, including the
    deprecated `auto_ptr<T>`, to determine in each case whether
    T = (the template instantiated for `int`) provides 
    `int & T::operator*() const` - which all of them in fact do.
*/ 
int main(void)
{
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
    return 0;
}

Costruito con GCC 4.6.3, il programma genera 110 - informandoci che T = std::shared_ptr<int> Non fornisce non la funzione int & T::operator*() const.

Se non sei già saggio di questo gotcha, allora uno sguardo alla definizione di std::shared_ptr<T> Nell'intestazione <memory> Farà luce. In quella implementazione, std::shared_ptr<T> È derivato da una classe base da cui eredita operator*() const. Quindi l'istanza del modello SFINAE<U, &U::operator*> Che costituisce la "ricerca" dell'operatore per U = std::shared_ptr<T> Non avverrà, perché std::shared_ptr<T> Non ha operator*() a sé stante e modello l'istanza non "fa l'ereditarietà".

Questo intoppo non influisce sul noto approccio SFINAE, usando "The sizeof () Trick", per rilevare semplicemente se T ha qualche funzione membro mf (vedi ad esempio questa risposta e commenti). Ma stabilire che T::mf Esiste spesso (di solito?) Non è abbastanza buono: potresti anche aver bisogno di stabilire che ha una firma desiderata. È qui che segna la tecnica illustrata. La variante puntata della firma desiderata è inscritta in un parametro di un tipo di modello che deve essere soddisfatto da &T::mf Affinché la sonda SFINAE abbia successo. Ma questa tecnica di istanziazione di template fornisce la risposta sbagliata quando T::mf Viene ereditato.

Una tecnica SFINAE sicura per l'introspezione compiletime di T::mf Deve evitare l'uso di &T::mf All'interno di un argomento modello per creare un'istanza di un tipo da cui dipende la risoluzione del modello di funzione SFINAE. Al contrario, la risoluzione della funzione del modello SFINAE può dipendere solo da dichiarazioni di tipo esattamente pertinenti utilizzate come tipi di argomento della funzione di sovraccarico SFINAE.

Per mezzo di una risposta alla domanda che rispetta questo vincolo, illustrerò per il rilevamento della compilazione di E T::operator*() const, per T e E arbitrari. Lo stesso modello verrà applicato mutatis mutandis al probe per qualsiasi altra firma del metodo membro.

#include <type_traits>

/*! The template `has_const_reference_op<T,E>` exports a
    boolean constant `value that is true iff `T` provides
    `E T::operator*() const`
*/ 
template< typename T, typename E>
struct has_const_reference_op
{
    /* SFINAE operator-has-correct-sig :) */
    template<typename A>
    static std::true_type test(E (A::*)() const) {
        return std::true_type();
    }

    /* SFINAE operator-exists :) */
    template <typename A> 
    static decltype(test(&A::operator*)) 
    test(decltype(&A::operator*),void *) {
        /* Operator exists. What about sig? */
        typedef decltype(test(&A::operator*)) return_type; 
        return return_type();
    }

    /* SFINAE game over :( */
    template<typename A>
    static std::false_type test(...) {
        return std::false_type(); 
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<T>(0,0)) type;

    static const bool value = type::value; /* Which is it? */
};

In questa soluzione, la funzione di sovraccarico della sonda SFINAE test() è "invocata in modo ricorsivo". (Ovviamente non è affatto invocato; ha semplicemente i tipi restituiti di invocazioni ipotetiche risolte dal compilatore.)

Dobbiamo esaminare almeno uno e al massimo due punti di informazione:

  • T::operator*() esiste affatto? Altrimenti, abbiamo finito.
  • Dato che T::operator*() esiste, è la sua firma E T::operator*() const?

Otteniamo le risposte valutando il tipo di ritorno di una singola chiamata su test(0,0). Questo è fatto da:

    typedef decltype(test<T>(0,0)) type;

Questa chiamata potrebbe essere risolta nel sovraccarico /* SFINAE operator-exists :) */ Di test(), oppure potrebbe risolversi nel sovraccarico /* SFINAE game over :( */. Non può risolvere il sovraccarico /* SFINAE operator-has-correct-sig :) */, Perché quello si aspetta solo un argomento e ne stiamo passando due.

Perché ne stiamo passando due? Basta forzare la risoluzione per escludere /* SFINAE operator-has-correct-sig :) */. Il secondo argomento non ha altro significato.

Questa chiamata a test(0,0) si risolverà in /* SFINAE operator-exists :) */ Nel caso in cui il primo argomento 0 soddisfi il primo tipo di parametro di quel sovraccarico, che è decltype(&A::operator*), con A = T. 0 soddisferà quel tipo nel caso in cui T::operator* Esista.

Supponiamo che il compilatore dica Sì a quello. Quindi sta andando con /* SFINAE operator-exists :) */ E deve determinare il tipo di ritorno della chiamata di funzione, che in quel caso è decltype(test(&A::operator*)) - il tipo di ritorno di un'altra ancora chiamata a test().

Questa volta, stiamo passando solo un argomento, &A::operator*, Che ora sappiamo esiste, o non saremmo qui. Una chiamata a test(&A::operator*) potrebbe risolversi in /* SFINAE operator-has-correct-sig :) */ O ancora per risolvere in /* SFINAE game over :( */. La chiamata corrisponderà a /* SFINAE operator-has-correct-sig :) */ Nel caso in cui &A::operator* Soddisfi il tipo di parametro singolo di quel sovraccarico, che è E (A::*)() const, con A = T.

Il compilatore dirà Sì qui se T::operator* Ha quella firma desiderata, e quindi deve nuovamente valutare il tipo di ritorno del sovraccarico. Non più "ricorsioni" ora: è std::true_type.

Se il compilatore non sceglie /* SFINAE operator-exists :) */ Per la chiamata test(0,0) o non sceglie /* SFINAE operator-has-correct-sig :) */ Per la chiamata test(&A::operator*), quindi in entrambi i casi va con /* SFINAE game over :( */ E il tipo di ritorno finale è std::false_type.

Ecco un programma di test che mostra il modello che produce le risposte attese in vari esempi di casi (di nuovo GCC 4.6.3).

// To test
struct empty{};

// To test 
struct int_ref
{
    int & operator*() const {
        return *_pint;
    }
    int & foo() const {
        return *_pint;
    }
    int * _pint;
};

// To test 
struct sub_int_ref : int_ref{};

// To test 
template<typename E>
struct ee_ref
{
    E & operator*() {
        return *_pe;
    }
    E & foo() const {
        return *_pe;
    }
    E * _pe;
};

// To test 
struct sub_ee_ref : ee_ref<char>{};

using namespace std;

#include <iostream>
#include <memory>
#include <vector>

int main(void)
{
    cout << "Expect Yes" << endl;
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value;
    cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
    cout << has_const_reference_op<std::vector<int>::const_iterator,
            int const &>::value;
    cout << has_const_reference_op<int_ref,int &>::value;
    cout << has_const_reference_op<sub_int_ref,int &>::value  << endl;
    cout << "Expect No" << endl;
    cout << has_const_reference_op<int *,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,char &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int>::value;
    cout << has_const_reference_op<unique_ptr<long>,int &>::value;
    cout << has_const_reference_op<int,int>::value;
    cout << has_const_reference_op<std::vector<int>,int &>::value;
    cout << has_const_reference_op<ee_ref<int>,int &>::value;
    cout << has_const_reference_op<sub_ee_ref,int &>::value;
    cout << has_const_reference_op<empty,int &>::value  << endl;
    return 0;
}

Ci sono nuovi difetti in questa idea? Può essere reso più generico senza ricadere ancora una volta nel fallo che evita?

35
Mike Kinghan

Ecco alcuni frammenti di utilizzo: * Le viscere di tutto ciò sono più in 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;

Controlla la classe membro x:

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

Verifica unione membri x:

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

Controlla la presenza dei membri x:

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

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

OR

CREATE_MEMBER_CHECKS(x);  //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

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

Questo dovrebbe essere sufficiente, se conosci il nome della funzione membro che ti aspetti. (In questo caso, la funzione bla non riesce a creare un'istanza se non esiste alcuna funzione membro (scriverne una che funzioni comunque è difficile perché manca una specializzazione parziale della funzione. Potrebbe essere necessario utilizzare i modelli di classe) Inoltre, l'abilitazione struct (che è simile a enable_if) potrebbe anche essere modellato sul tipo di funzione che si desidera che abbia come membro.

template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
  A a;
  B b;
  bla(b);
  bla(a);
}
11
coppro

Per fare ciò avremo bisogno di usare:

  1. Sovraccarico del modello di funzione con tipi di ritorno diversi a seconda che il metodo sia disponibile
  2. In linea con i meta-condizionali nell'intestazione type_traits , vorremmo restituire un true_type O false_type = dai nostri sovraccarichi
  3. Dichiarare il sovraccarico true_type In attesa di un int e il sovraccarico false_type Che si aspetta che i parametri variabili vengano sfruttati: "La priorità più bassa della conversione dell'ellissi nella risoluzione del sovraccarico" =
  4. Nel definire la specifica del modello per la funzione true_type Useremo declval e decltype permettendoci di rilevare la funzione indipendentemente dalle differenze del tipo di ritorno o sovraccarichi tra i metodi

Puoi vedere un esempio dal vivo di questo qui . Ma lo spiegherò anche di seguito:

Voglio verificare l'esistenza di una funzione chiamata test che accetta un tipo convertibile da int, quindi dovrei dichiarare queste due funzioni:

template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
  • decltype(hasTest<a>(0))::value è true (Nota che non è necessario creare funzionalità speciali per gestire il sovraccarico void a::test(), void a::test(int) è accettato)
  • decltype(hasTest<b>(0))::value è true (poiché int è convertibile in doubleint b::test(double) è accettato, indipendentemente dal tipo restituito)
  • decltype(hasTest<c>(0))::value è false (c non ha un metodo chiamato test che accetta un tipo convertibile da int per questo non è accettato )

Questa soluzione presenta 2 inconvenienti:

  1. Richiede una dichiarazione per metodo di una coppia di funzioni
  2. Crea inquinamento dello spazio dei nomi, in particolare se vogliamo testare nomi simili, ad esempio come chiameremmo una funzione che voleva testare un metodo test()?

Quindi è importante che queste funzioni siano dichiarate in uno spazio dei nomi dei dettagli, o idealmente se devono essere usate solo con una classe, dovrebbero essere dichiarate privatamente da quella classe. A tal fine ho scritto una macro per aiutarti a sottrarre queste informazioni:

#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
                              template <typename T> static false_type __ ## DEFINE(...); \
                              template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));

Puoi usarlo come:

namespace details {
    FOO(test(declval<int>()), test_int)
    FOO(test(), test_void)
}

Successivamente chiamare details::test_int<a>::value O details::test_void<a>::value Produrrebbe true o false ai fini del codice inline o della meta-programmazione.

5
Jonathan Mee

Ecco una versione più semplice della risposta di Mike Kinghan. Questo rileverà i metodi ereditati. Controllerà anche la firma esatta (a differenza dell'approccio di jrok che consente le conversioni di argomenti).

template <class C>
class HasGreetMethod
{
    template <class T>
    static std::true_type testSignature(void (T::*)(const char*) const);

    template <class T>
    static decltype(testSignature(&T::greet)) test(std::nullptr_t);

    template <class T>
    static std::false_type test(...);

public:
    using type = decltype(test<C>(nullptr));
    static const bool value = type::value;
};

struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");

Eseguibile esempio

5
Valentin Milea

Puoi usare std :: is_member_function_pointer

class A {
   public:
     void foo() {};
}

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
4
Yochai Timmer

Sono venuto con lo stesso tipo di problema e ho trovato le soluzioni proposte molto interessanti ... ma avevano il requisito di una soluzione che:

  1. Rileva anche le funzioni ereditate;
  2. È compatibile con compilatori non C++ 11 pronti (quindi senza decltype)

Trovato un altro thread proponendo qualcosa del genere, basato su un discussione BOOST . Ecco la generalizzazione della soluzione proposta come dichiarazione di due macro per la classe dei tratti, seguendo il modello delle classi boost :: has _ * .

#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>

/// Has constant function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)

/// Has non-const function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)

// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...)  \
    template                                                                  \
    <   typename Type,                                                        \
        bool is_class = boost::is_class<Type>::value                          \
    >                                                                         \
    class has_func_ ## func_name;                                             \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,false>                                  \
    {public:                                                                  \
        BOOST_STATIC_CONSTANT( bool, value = false );                         \
        typedef boost::false_type type;                                       \
    };                                                                        \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,true>                                   \
    {   struct yes { char _foo; };                                            \
        struct no { yes _foo[2]; };                                           \
        struct Fallback                                                       \
        {   func_ret_type func_name( __VA_ARGS__ )                            \
                UTILITY_OPTIONAL(func_const,const) {}                         \
        };                                                                    \
        struct Derived : public Type, public Fallback {};                     \
        template <typename T, T t>  class Helper{};                           \
        template <typename U>                                                 \
        static no deduce(U*, Helper                                           \
            <   func_ret_type (Fallback::*)( __VA_ARGS__ )                    \
                    UTILITY_OPTIONAL(func_const,const),                       \
                &U::func_name                                                 \
            >* = 0                                                            \
        );                                                                    \
        static yes deduce(...);                                               \
    public:                                                                   \
        BOOST_STATIC_CONSTANT(                                                \
            bool,                                                             \
            value = sizeof(yes)                                               \
                == sizeof( deduce( static_cast<Derived*>(0) ) )               \
        );                                                                    \
        typedef ::boost::integral_constant<bool,value> type;                  \
        BOOST_STATIC_CONSTANT(bool, is_const = func_const);                   \
        typedef func_ret_type return_type;                                    \
        typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;                \
    }

// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__

Queste macro si espandono in una classe di tratti con il seguente prototipo:

template<class T>
class has_func_[func_name]
{
public:
    /// Function definition result value
    /** Tells if the tested function is defined for type T or not.
    */
    static const bool value = true | false;

    /// Function definition result type
    /** Type representing the value attribute usable in
        http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
    */
    typedef boost::integral_constant<bool,value> type;

    /// Tested function constness indicator
    /** Indicates if the tested function is const or not.
        This value is not deduced, it is forced depending
        on the user call to one of the traits generators.
    */
    static const bool is_const = true | false;

    /// Tested function return type
    /** Indicates the return type of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef func_ret_type return_type;

    /// Tested function arguments types
    /** Indicates the arguments types of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};

Quindi qual è l'uso tipico che si può fare da questo?

// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
    // Next line will declare the traits class
    // to detect the member function void foo(int,int) const
    DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}

// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>

// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   _this_.foo(a,b);
}

// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   default_foo(_this_,a,b);
}

// Let us declare test types
struct empty
{
};
struct direct_foo
{
    void foo(int,int);
};
struct direct_const_foo
{
    void foo(int,int) const;
};
struct inherited_const_foo :
    public direct_const_foo
{
};

// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
    int a;
    foo_bar(a); // calls default_foo

    empty b;
    foo_bar(b); // calls default_foo

    direct_foo c;
    foo_bar(c); // calls default_foo (member function is not const)

    direct_const_foo d;
    foo_bar(d); // calls d.foo (member function is const)

    inherited_const_foo e;
    foo_bar(e); // calls e.foo (inherited member function)
}
4
S. Paris

Per non essere invadente, puoi anche inserire serialize nello spazio dei nomi della classe in fase di serializzazione o della classe di archivio, grazie a Ricerca Koenig . Vedi Namespace for Free Function Overrides per maggiori dettagli. :-)

Aprire un determinato spazio dei nomi per implementare una funzione gratuita è semplicemente sbagliato. (ad esempio, non dovresti aprire lo spazio dei nomi std per implementare swap per i tuoi tipi, ma dovresti invece utilizzare la ricerca Koenig.)

3

Va bene. Secondo tentativo. Va bene se non ti piace neanche questo, sto cercando altre idee.

L'articolo di Herb Sutter parla di tratti. Quindi puoi avere una classe di tratti la cui istanza predefinita ha il comportamento di fallback e per ogni classe in cui esiste la tua funzione membro, la classe di tratti è specializzata per invocare la funzione membro. Credo che l'articolo di Herb menzioni una tecnica per fare questo in modo che non implichi molte copie e incolla.

Come ho detto, però, forse non vuoi il lavoro extra coinvolto nelle classi di "tagging" che implementano quel membro. Nel qual caso, sto guardando una terza soluzione ...

2

Senza il supporto C++ 11 (decltype) questo potrebbe funzionare:

SSCCE

#include <iostream>
using namespace std;

struct A { void foo(void); };
struct Aa: public A { };
struct B { };

struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };

template<typename T>
struct FooFinder {
    typedef char true_type[1];
    typedef char false_type[2];

    template<int>
    struct TypeSink;

    template<class U>
    static true_type &match(U);

    template<class U>
    static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);

    template<class U>
    static false_type &test(...);

    enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};

int main() {
    cout << FooFinder<A>::value << endl;
    cout << FooFinder<Aa>::value << endl;
    cout << FooFinder<B>::value << endl;

    cout << FooFinder<retA>::value << endl;
    cout << FooFinder<argA>::value << endl;
    cout << FooFinder<constA>::value << endl;
    cout << FooFinder<varA>::value << endl;
}

Speriamo che funzioni

A, Aa e B sono i clasi in questione, Aa è quello speciale che eredita il membro che stiamo cercando.

Nel FooFinder il true_type e false_type sono le sostituzioni per le corrispondenti classi C++ 11. Anche per la comprensione della meta-programmazione dei template, rivelano le basi stesse del trucco SFINAE.

TypeSink è una struttura di modello che viene utilizzata in seguito per affondare il risultato integrale dell'operatore sizeof in un'istanza di modello per formare un tipo.

La funzione match è un altro tipo di modello SFINAE che rimane senza una controparte generica. Può quindi essere istanziato solo se il tipo del suo argomento corrisponde al tipo per cui era specializzato.

Entrambe le funzioni test insieme alla dichiarazione enum formano infine il modello SFINAE centrale. Ce n'è uno generico che usa un'ellissi che restituisce il false_type e una controparte con argomenti più specifici per avere la precedenza.

Per poter istanziare la funzione test con un argomento modello di T, è necessario creare un'istanza della funzione match, poiché è necessario il suo tipo restituito per istanziare TypeSink discussione. L'avvertimento è che &U::foo, essendo racchiuso in un argomento di funzione, è non a cui fa riferimento all'interno di una specializzazione di argomento modello, quindi la ricerca dei membri ereditati ha ancora luogo.

1
Kamajii

Credo che la risposta che stai cercando sia qui.

http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Compile-Time

e un esempio leggermente più completo qui

http://pastie.org/298994

Uso la tecnica per rilevare la presenza di un operatore ostream di supporto ostream << sulla classe in questione e quindi generare un diverso bit di codice a seconda.

Non credevo fosse possibile prima di trovare la soluzione collegata, ma è un trucco molto accurato. Trascorrere del tempo a comprendere il codice e ne vale la pena.

Chiodo senza testa

0
Brad Phelan

Se stai usando la follia di Facebook, la loro macro è pronta all'uso per aiutarti:

#include <folly/Traits.h>
namespace {
  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace

void some_func() {
  cout << "Does class Foo have a member int test() const? "
    << boolalpha << has_test_traits<Foo, int() const>::value;
}

Sebbene i dettagli dell'implementazione siano gli stessi della risposta precedente, l'uso di una libreria è più semplice.

0