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


135

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 C 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?



@ R.MartinhoFernandes Che tipo di risposta stai cercando? Questa risposta di Mike Kinghan è abbastanza approfondita e utilizza materiale C ++ 11.
jrok,

@ R.MartinhoFernandes Forse questa è la versione moderna che stai cercando?
Daniel Frey,

Risposte:


90

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

14
cos'e 'questo??? è un codice c ++ legale ?? puoi scrivere "template <typename U, size_t (U :: *) () const>" ?? ma ... è un'ottima e nuova soluzione! Ti ringrazio, analizzerò meglio domani con i miei colleghi ... fantastico!
ugasoft,

2
Nell'esempio manca la definizione di 'int_to_type'. Ovviamente non si aggiunge alla risposta, ma significa che le persone possono vedere il tuo codice in azione dopo un rapido taglio e incolla.
Richard Corden,

2
Una semplice definizione di int_to_type potrebbe essere: 'template <int N> struct int_to_type {};'. Molte implementazioni mantengono il valore del parametro N in un enum oppure in una costante intera statica (template <int N> struct int_to_type {enum {value = N};}; / template <int N> struct int_to_type {static const int value = N;})
David Rodríguez - dribeas,

2
Prendi semplicemente boost :: integral_constant invece di int_to_type.
Vadim Ferderer,

2
@JohanLundberg È una funzione membro puntatore a (non statico). Ad esempio size_t(std::vector::*p)() = &std::vector::size;,.
Ripristina Monica il

133

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 nella sua risposta ).

La funzione testata da questo frammento viene chiamata 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

Funziona se Y non ha un metodo chiamato "serializzare"? Non vedo come restituirebbe un valore falso se il metodo "serializza" non esistesse.
Collin,

1
@Collin in tal caso la sostituzione del parametro template non riesce per il primo sovraccarico del controllo ed è scartata dal set di sovraccarico. Ritorna al secondo che restituisce false_type. Questo non è un errore del compilatore perché principio SFINAE.
jrok,

1
@ elios264 Non c'è. È possibile utilizzare una macro per scrivere un modello per ciascuna funzione che si desidera verificare.
jrok

1
Qualche motivo particolare per cui l'argomento per il controllo è di tipo T * anziché T o T &?
Shibumi

1
Ma cosa succede se lo serializestesso accetta un modello. C'è un modo per testare l' serializeesistenza senza digitare il tipo esatto?
Hi-Angel,

37

La risposta accettata a questa domanda sull'introspezione della funzione membro di compiletime, sebbene sia giustamente popolare, ha un problema 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, i risultati del programma 110- ci informano che T = std::shared_ptr<int>fa non forniscono int & T::operator*() const.

Se non sei già saggio con questo gotcha, allora uno sguardo alla definizione di std::shared_ptr<T>nell'intestazione <memory>farà luce. In tale attuazione,std::shared_ptr<T> deriva da una classe base da cui eredita operator*() const. Quindi l'istanza del modello SFINAE<U, &U::operator*>che costituisce la "ricerca" per l'operatore U = std::shared_ptr<T>non avverrà, perché std::shared_ptr<T>non ha alcun operator*()diritto e l'istanza del modello non "fa l'ereditarietà".

Questo intoppo non influisce sul noto approccio SFINAE, usando "The sizeof () Trick", per rilevare semplicemente se Tha qualche funzione membro mf(vedi ad es. questa risposta e commenti). Ma stabilire che T::mfesiste 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 &T::mfaffinché la sonda SFINAE abbia successo. Ma questa tecnica di istanza di modello fornisce la risposta sbagliata quando T::mfviene ereditata.

Una tecnica SFINAE sicura per l'introspezione di compiletime T::mfdeve evitare l'uso &T::mfall'interno di un argomento template per creare un'istanza di un tipo da cui dipende la risoluzione del template della 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.

A titolo di risposta alla domanda che si attiene a questo vincolo, illustrerò per il rilevamento della compilazione di E T::operator*() const, per arbitrario Te E. Lo stesso modello si applicherà, 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() viene "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:

  • fa T::operator*() 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 a test(0,0). Questo è fatto da:

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

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

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

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

Supponiamo che il compilatore dica Sì a quello. Quindi sta andando avanti /* 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 chiamata ancora 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 nuovamente in cui risolvere /* SFINAE game over :( */. La chiamata corrisponderà /* 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 dovrà nuovamente valutare il tipo di ritorno del sovraccarico. Niente più "ricorsioni" ora: lo è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*), in entrambi i casi va bene /* 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?


16

Ecco alcuni frammenti di utilizzo: * Le viscere di tutto ciò sono più in basso

Verifica la presenza di un membro xin 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;

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

Verifica 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 unione membri x:

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

Verifica la presenza dei membri x:

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

Verificare la presenza di qualsiasi funzione membro xindipendentemente 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)

1
Questo è fantastico; sarebbe bello metterlo in una singola libreria di file di intestazione.
Allan,

12

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

4
Thaks! è simile alla soluzione proposta da yrp. Non sapevo che il modello potesse essere modellato sulle funzioni dei membri. Questa è una nuova funzionalità che ho imparato oggi! ... e una nuova lezione: "non dire mai che sei esperto di c ++" :)
ugasoft,

7

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

Esempio eseguibile


Questo va bene, ma non funzionerà se la funzione non prende argomenti
Triskeldeian,

Funziona benissimo. Non ho avuto problemi ad applicare questo trucco alle funzioni dei membri senza prendere argomenti.
Giovanni B

Questo funziona bene per me con argomenti multipli e senza metodo, compresi i sovraccarichi e anche con l'ereditarietà e con l'uso di usingportare sovraccarichi dalla classe base. Funziona per me su MSVC 2015 e con Clang-CL. Tuttavia, non funziona con MSVC 2012.
Steve

5

È possibile utilizzare std :: is_member_function_pointer

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

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

16
Non &A::foosarà un errore di compilazione se non ce n'è fooaffatto A? Ho letto la domanda originale come dovrebbe funzionare con qualsiasi classe di input, non solo con una sorta di membro chiamato foo.
Jeff Walden,

5

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)

Ho trovato un altro thread che proponeva qualcosa del genere, basato su una discussione BOOST . Ecco la generalizzazione della soluzione proposta come dichiarazione di due macro per la classe 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)
}

5

Per fare ciò avremo bisogno di usare:

  1. Sovraccarico del modello di funzione con tipi di ritorno diversi a seconda della disponibilità del metodo
  2. In linea con i meta-condizionali type_traitsnell'intestazione, vorremmo restituire uno true_typeofalse_type dai nostri sovraccarichi
  3. Dichiarare il true_typesovraccarico in attesa di un inte il false_typesovraccarico in attesa di sfruttare i parametri Variadic: "La priorità più bassa della conversione dei puntini di sospensione nella risoluzione del sovraccarico"
  4. Nel definire le specifiche del modello per la true_typefunzione che useremo declvale decltypeconsentendoci di rilevare la funzione indipendentemente dalle differenze del tipo di ritorno o dai 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 da testcui prende un tipo convertibile 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 void a::test()sovraccarico, void a::test(int)è accettato)
  • decltype(hasTest<b>(0))::valueè true(poiché intè convertibile in double int b::test(double)è accettato, indipendentemente dal tipo restituito)
  • decltype(hasTest<c>(0))::valueè false( cnon ha un metodo chiamato testche accetta un tipo convertibile da intciò 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 test()metodo?

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 chiama details::test_int<a>::valueo details::test_void<a>::valuecederebbe trueo falseai fini del codice inline o della meta-programmazione.


3

Per non essere invadenti, puoi anche inserire serializelo spazio dei nomi della classe in fase di serializzazione o della classe di archivio, grazie alla ricerca Koenig . Vedi Spazi dei nomi per Sostituzione funzioni gratuite 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 stdda implementare swapper i tuoi tipi, ma dovresti invece utilizzare la ricerca Koenig.)


3

Sembra che tu voglia il linguaggio del rivelatore. Le risposte sopra sono variazioni su questo che funzionano con C ++ 11 o C ++ 14.

La std::experimentallibreria ha funzionalità che fanno essenzialmente questo. Rielaborando un esempio dall'alto, potrebbe essere:

#include <experimental/type_traits>

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;

Se non puoi usare std :: sperimentale, una versione rudimentale può essere fatta in questo modo:

template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;

Dal momento che has_serialize_t è davvero o std :: true_type o std :: false_type, può essere utilizzato tramite uno dei comuni modi di dire SFINAE:

template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}

O utilizzando la spedizione con risoluzione di sovraccarico:

template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
  // call serialize here.
}

template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
  // do something else here.
}

template<class T>
std::string Serialize(const T& t) {
  return SerializeImpl(has_serialize_t<T>{}, t);
}

2

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 comporti 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 cercando una terza soluzione ...


eh ... ho analizzato questa soluzione ... Penso che sia un po 'troppo costoso per gli utenti del mio framework. (ok, lo ammetto, sto sviluppando un framework di streaming e sto scegliendo tra l'estensione di iostream o la riscrittura di qualcosa di più semplice)
ugasoft,

La mia terza soluzione sarebbe quella di usare SFINAE. Dal momento che la risposta di yrp lo menziona già, non ci entrerò (perché sto ancora studiando su di esso: conosco l'idea, ma il diavolo è nei dettagli), a meno che la sua soluzione non funzioni per te alla fine . :-)
Chris Jester-Young,

1

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, Aae Bsono i clasi in questione, Aaessendo quello speciale che eredita il membro che stiamo cercando.

Nella FooFinderla true_typee false_typesono le sostituzioni per il corrispondente C ++ 11 classi. Anche per la comprensione della meta-programmazione dei template, rivelano le basi stesse del trucco SFINAE.

La TypeSinkè una struct modello utilizzato successivamente per affondare il risultato integrale del sizeofoperatore in un'istanza modello per formare un tipo.

La matchfunzione è 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 testfunzioni insieme alla dichiarazione enum formano infine il modello centrale SFINAE. Ce n'è uno generico che usa un'ellissi che restituisce la false_typee una controparte con argomenti più specifici per avere la precedenza.

Per poter istanziare la testfunzione con un argomento template di T, la matchfunzione deve essere istanziata, poiché per istanziare l' TypeSinkargomento è necessario il suo tipo restituito . L'avvertenza è che &U::foo, essendo racchiuso in un argomento di funzione, non si fa riferimento all'interno di una specializzazione di argomento modello, quindi la ricerca dei membri ereditati ha ancora luogo.


1

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 di implementazione siano gli stessi della risposta precedente, l'uso di una libreria è più semplice.


0

Avevo un bisogno simile e mi sono imbattuto in questo SO. Ci sono molte soluzioni interessanti / potenti proposte qui, anche se è un po 'lungo solo per un'esigenza specifica: rilevare se una classe ha una funzione membro con una firma precisa. Così ho fatto qualche lettura / test e ho trovato la mia versione che potrebbe essere di interesse. Rileva:

  • funzione membro statico
  • funzione membro non statica
  • const non funzione membro statico

con una firma precisa. Dal momento che non ho bisogno di catturare alcuna firma (che richiederebbe una soluzione più complicata), questa mi sembra. Fondamentalmente ha usato enable_if_t .

struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };

// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
                        std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T> 
                      > : std::true_type {};

template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
                  std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
                > : std::true_type {};

template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <typename T,
                        std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
                      > : std::true_type {};

int main ()
{
    constexpr bool has_sum_val = has_static_sum<Foo>::value;
    constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;

    constexpr bool has_calc_val = has_calc<Bar>::value;
    constexpr bool not_has_calc_val = !has_calc<Foo>::value;

    constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
    constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;

    std::cout<< "           has_sum_val " << has_sum_val            << std::endl
             << "       not_has_sum_val " << not_has_sum_val        << std::endl
             << "          has_calc_val " << has_calc_val           << std::endl
             << "      not_has_calc_val " << not_has_calc_val       << std::endl
             << "    has_calc_const_val " << has_calc_const_val     << std::endl
             << "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}

Produzione :

           has_sum_val 1
       not_has_sum_val 1
          has_calc_val 1
      not_has_calc_val 1
    has_calc_const_val 1
not_has_calc_const_val 1

0

Sulla JROK 's risposta , ho evitato di usare classi template nidificate e / o funzioni.

#include <type_traits>

#define CHECK_NESTED_FUNC(fName) \
    template <typename, typename, typename = std::void_t<>> \
    struct _has_##fName \
    : public std::false_type {}; \
    \
    template <typename Class, typename Ret, typename... Args> \
    struct _has_##fName<Class, Ret(Args...), \
        std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
    : public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
    {}; \
    \
    template <typename Class, typename Signature> \
    using has_##fName = _has_##fName<Class, Signature>;

#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value

Possiamo usare le macro sopra come sotto:

class Foo
{
public:
    void Bar(int, const char *) {}
};

CHECK_NESTED_FUNC(Bar);  // generate required metafunctions

int main()
{
    using namespace std;
    cout << boolalpha
         << HAS_NESTED_FUNC(Foo, Bar, void(int, const char *))  // prints true
         << endl;
    return 0;
}

Suggerimenti sono ben accetti

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.