2012-01-31 11 views
12

Come posso dedurre staticamente se un argomento è un oggetto funzione C++ (functor)?È possibile una classe di trait di C++ is_functor?

template <typename F> 
void test(F f) {} 

Ho provato is_function<F>::value, ma questo non funziona. Sembra anche che non ci sia il tratto is_functor, quindi forse non è possibile. Sembra che stia cercando solo una funzione membro specifica, in questo caso l'operatore di chiamata di funzione: F::operator().

+0

come dire 'is_function :: value'? – Fiktik

+1

http://groups.google.com/group/comp.lang.c++.moderated/msg/e5fbc9305539f699 potrebbe interessarti. – pmr

+1

Vuoi testare solo i funtori o qualsiasi oggetto callable? Sembra che un uso di SFINAE del carattere 'result_of' possa funzionare per identificare qualsiasi tipo di chiamabile. Sono un po 'sorpreso dal fatto che non ci sia già alcun tratto di 'std :: is_callable'. – bames53

risposta

0
template<typename T, typename Sign>         
struct is_functor 
{                 
    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::operator()>*); 
    template <typename > static no &chk(...);      
    static bool const value = sizeof(chk<T>(nullptr)) == sizeof(yes);  
}; 

Alterato da this answer.

Potrebbe essere usato come ...

template<typename T> 
typename std::enable_if<is_functor<T, void(T::*)()>::value>::type func() 
{ 
} 
+0

'typename decltype'? E la tua soluzione non funziona se l'operatore '()' è sovraccarico. – kennytm

+0

La tua definizione di functor è incompleta. Un funtore standard è un puntatore a funzione o un oggetto con 'operatore()' sovraccarico. –

+0

Ho pubblicato una soluzione diversa; @MaximYegorushkin ma il nuovo non cambia in merito, hmmm – David

12

E 'possibile creare tale caratteristica, con due limitazioni:

  1. per il compilatore, una funzione libera è qualcosa di fondamentalmente diverso da un funtore di classe che sovraccarica lo operator(). Quindi dobbiamo trattare entrambi i casi separatamente durante l'implementazione. Tuttavia, questo non è un problema per l'utilizzo, possiamo nascondere questi dettagli di implementazione all'utente.
  2. Abbiamo bisogno di conoscere la firma della funzione che si desidera chiamare. Questo di solito non è un problema, e ha il bell'effetto collaterale che il nostro tratto è in grado di gestire i sovraccarichi in modo abbastanza nativo.

Fase uno: Gratis funzioni

Cominciamo con le funzioni liberi, perché sono po 'più facile da individuare. Il nostro compito è, quando viene dato un puntatore a una funzione, determinare se la firma di quel puntatore a funzione corrisponde alla firma passata come secondo argomento modello. Per poterli confrontare, abbiamo bisogno di capire la firma della funzione sottostante o creare un puntatore a funzione della nostra firma. Ho arbitrariamente scelto la seconda:

// build R (*)(Args...) from R (Args...) 
// compile error if signature is not a valid function signature 
template <typename, typename> 
struct build_free_function; 

template <typename F, typename R, typename ... Args> 
struct build_free_function<F, R (Args...)> 
{ using type = R (*)(Args...); }; 

Ora tutto quello che resta da fare è quello di confrontare e abbiamo finito con la funzione di parte libera:

// determine whether a free function pointer F has signature S 
template <typename F, typename S> 
struct is_function_with_signature 
{ 
    // check whether F and the function pointer of S are of the same 
    // type 
    static bool constexpr value = std::is_same< 
     F, typename build_free_function<F, S>::type 
    >::value; 
}; 

Fase due: funtori Classe

Questo è un po 'più coinvolto. Si potrebbe facilmente rilevare con SFINAE se una classe definisce un operator():

template <typename T> 
struct defines_functor_operator 
{ 
    typedef char (& yes)[1]; 
    typedef char (& no)[2]; 

    // we need a template here to enable SFINAE 
    template <typename U> 
    static yes deduce(char (*)[sizeof(&U::operator())]); 
    // fallback 
    template <typename> static no deduce(...); 

    static bool constexpr value = sizeof(deduce<T>(0)) == sizeof(yes); 
}; 

ma questo non ci dice se esiste uno per la nostra firma funzione desiderata! Fortunatamente, possiamo usare un trucco qui: i puntatori sono parametri di template validi. Così siamo in grado di utilizzare prima il puntatore a funzione membro della nostra firma desiderata, e controllare se &T::operator() è di quel tipo:

template <typename T, T> struct check; 

Ora check<void (C::*)() const, &C::operator()> sarà solo un modello di istanza valida se C ha effettivamente un void C::operator()() const. Ma per fare questo dobbiamo prima combinare C e la firma in un puntatore funzione membro. Come abbiamo già visto, dobbiamo preoccuparci di due casi extra di cui non abbiamo avuto a cuore le funzioni gratuite: const e volatile.Oltre a questo è più o meno lo stesso:

// build R (C::*)(Args...) from R (Args...) 
//  R (C::*)(Args...) const from R (Args...) const 
//  R (C::*)(Args...) volatile from R (Args...) volatile 
// compile error if signature is not a valid member function signature 
template <typename, typename> 
struct build_class_function; 

template <typename C, typename R, typename ... Args> 
struct build_class_function<C, R (Args...)> 
{ using type = R (C::*)(Args...); }; 

template <typename C, typename R, typename ... Args> 
struct build_class_function<C, R (Args...) const> 
{ using type = R (C::*)(Args...) const; }; 

template <typename C, typename R, typename ... Args> 
struct build_class_function<C, R (Args...) volatile> 
{ using type = R (C::*)(Args...) volatile; }; 

Mettendo questo e le nostre scoperte, relativa alla struct check aiutante insieme, otteniamo il nostro metafunction di controllo per gli oggetti funtore:

// determine whether a class C has an operator() with signature S 
template <typename C, typename S> 
struct is_functor_with_signature 
{ 
    typedef char (& yes)[1]; 
    typedef char (& no)[2]; 

    // helper struct to determine that C::operator() does indeed have 
    // the desired signature; &C::operator() is only of type 
    // R (C::*)(Args...) if this is true 
    template <typename T, T> struct check; 

    // T is needed to enable SFINAE 
    template <typename T> static yes deduce(check< 
     typename build_class_function<C, S>::type, &T::operator()> *); 
    // fallback if check helper could not be built 
    template <typename> static no deduce(...); 

    static bool constexpr value = sizeof(deduce<C>(0)) == sizeof(yes); 
}; 

Fase tre: Mettere i pezzi insieme

Abbiamo quasi finito. Ora abbiamo solo bisogno di decidere quando utilizzare la nostra funzione gratuita e quando i metafunzionamenti del functor della classe. Fortunatamente, C++ 11 ci fornisce un tratto std::is_class che possiamo usare per questo. Quindi tutto quello che dobbiamo fare è specializzarsi su un parametro booleano:

// C is a class, delegate to is_functor_with_signature 
template <typename C, typename S, bool> 
struct is_callable_impl 
    : std::integral_constant< 
     bool, is_functor_with_signature<C, S>::value 
     > 
{}; 

// F is not a class, delegate to is_function_with_signature 
template <typename F, typename S> 
struct is_callable_impl<F, S, false> 
    : std::integral_constant< 
     bool, is_function_with_signature<F, S>::value 
     > 
{}; 

in modo che possiamo finalmente aggiungere l'ultimo pezzo del puzzle, essendo il nostro attuale is_callable tratto:

// Determine whether type Callable is callable with signature Signature. 
// Compliant with functors, i.e. classes that declare operator(); and free 
// function pointers: R (*)(Args...), but not R (Args...)! 
template <typename Callable, typename Signature> 
struct is_callable 
    : is_callable_impl< 
     Callable, Signature, 
     std::is_class<Callable>::value 
     > 
{}; 

Ora ripulire il nostro codice, inserire i dettagli dell'implementazione in namespace anonimi in modo che non siano accessibili al di fuori del nostro file e avere un buon is_callable.hpp da utilizzare nel nostro progetto.

codice Figura

namespace // implementation detail 
{ 
    // build R (*)(Args...) from R (Args...) 
    // compile error if signature is not a valid function signature 
    template <typename, typename> 
    struct build_free_function; 

    template <typename F, typename R, typename ... Args> 
    struct build_free_function<F, R (Args...)> 
    { using type = R (*)(Args...); }; 

    // build R (C::*)(Args...) from R (Args...) 
    //  R (C::*)(Args...) const from R (Args...) const 
    //  R (C::*)(Args...) volatile from R (Args...) volatile 
    // compile error if signature is not a valid member function signature 
    template <typename, typename> 
    struct build_class_function; 

    template <typename C, typename R, typename ... Args> 
    struct build_class_function<C, R (Args...)> 
    { using type = R (C::*)(Args...); }; 

    template <typename C, typename R, typename ... Args> 
    struct build_class_function<C, R (Args...) const> 
    { using type = R (C::*)(Args...) const; }; 

    template <typename C, typename R, typename ... Args> 
    struct build_class_function<C, R (Args...) volatile> 
    { using type = R (C::*)(Args...) volatile; }; 

    // determine whether a class C has an operator() with signature S 
    template <typename C, typename S> 
    struct is_functor_with_signature 
    { 
     typedef char (& yes)[1]; 
     typedef char (& no)[2]; 

     // helper struct to determine that C::operator() does indeed have 
     // the desired signature; &C::operator() is only of type 
     // R (C::*)(Args...) if this is true 
     template <typename T, T> struct check; 

     // T is needed to enable SFINAE 
     template <typename T> static yes deduce(check< 
      typename build_class_function<C, S>::type, &T::operator()> *); 
     // fallback if check helper could not be built 
     template <typename> static no deduce(...); 

     static bool constexpr value = sizeof(deduce<C>(0)) == sizeof(yes); 
    }; 

    // determine whether a free function pointer F has signature S 
    template <typename F, typename S> 
    struct is_function_with_signature 
    { 
     // check whether F and the function pointer of S are of the same 
     // type 
     static bool constexpr value = std::is_same< 
      F, typename build_free_function<F, S>::type 
     >::value; 
    }; 

    // C is a class, delegate to is_functor_with_signature 
    template <typename C, typename S, bool> 
    struct is_callable_impl 
     : std::integral_constant< 
      bool, is_functor_with_signature<C, S>::value 
      > 
    {}; 

    // F is not a class, delegate to is_function_with_signature 
    template <typename F, typename S> 
    struct is_callable_impl<F, S, false> 
     : std::integral_constant< 
      bool, is_function_with_signature<F, S>::value 
      > 
    {}; 
} 

// Determine whether type Callable is callable with signature Signature. 
// Compliant with functors, i.e. classes that declare operator(); and free 
// function pointers: R (*)(Args...), but not R (Args...)! 
template <typename Callable, typename Signature> 
struct is_callable 
    : is_callable_impl< 
     Callable, Signature, 
     std::is_class<Callable>::value 
     > 
{}; 

Ideone esempio con alcuni test

http://ideone.com/7PWdiv

+0

Wow.Wow.Wow.Wow.Wow. – mark

0

Anche se questo non funziona per funzioni sovraccaricate, per tutti gli altri casi (funzioni libere, classi di attuazione operator() e lambda) queste soluzioni brevi funzionano in C++ 11:

template <typename T, typename Signature> 
struct is_callable: std::is_convertible<T,std::function<Signature>> { }; 

Nota: std::is_callable sarà disponibile in C++ 17.

Problemi correlati