2012-01-31 14 views
12

¿Cómo puedo deducir estáticamente si un argumento es un objeto de función C++ (functor)?¿Es posible una clase de rasgo is_functor C++?

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

Probé is_function<F>::value, pero esto no funciona. También parece que no existe el rasgo is_functor, por lo que tal vez no sea posible. Parece que solo estoy buscando una función miembro específica, en este caso, el operador de llamada a función: F::operator().

+0

¿qué tal 'is_function :: value'? – Fiktik

+1

http://groups.google.com/group/comp.lang.c++moderated/msg/e5fbc9305539f699 podría ser de su interés. – pmr

+1

¿Solo desea probar funtores o cualquier objeto invocable? Parece que algún uso de SFINAE del rasgo 'result_of' funcionaría para identificar cualquier tipo invocable. Estoy un poco sorprendido de que no parece haber ningún rasgo de 'std :: is_callable'. – bames53

Respuesta

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

Altered from this answer.

Podría ser utilizado como ...

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

'typename decltype'? Y su solución no funciona si el 'operador()' está sobrecargado. – kennytm

+0

Su definición de functor está incompleta. Un functor estándar es un puntero de función o un objeto con 'operador()' sobrecargado. –

+0

Publiqué una solución diferente; @MaximYegorushkin pero el nuevo no cambia con respecto a eso, hmmm – David

12

Es posible crear un rasgo de este tipo, con dos restricciones:

  1. Para el compilador, una función libre es algo fundamentalmente diferente de un functor de clase que sobrecarga operator(). Por lo tanto, tenemos que tratar ambos casos por separado cuando se implementa. Sin embargo, esto no es un problema de uso, podemos ocultar este detalle de implementación al usuario.
  2. Necesitamos saber la firma de la función que desea llamar. Esto generalmente no es un problema, y ​​tiene el agradable efecto secundario de que nuestro rasgo es capaz de manejar sobrecargas de forma bastante nativa.

Primer paso: Libre funciones

Vamos a empezar con funciones gratuitas, ya que son poco más fácil de detectar. Nuestra tarea es, cuando se le da un puntero a la función, determinar si la firma de ese puntero de función coincide con la firma pasada como el segundo argumento de la plantilla. Para poder compararlos, necesitamos comprender la firma de la función subyacente o crear un puntero a la función de nuestra firma. Me eligió arbitrariamente este último:

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

Ahora todo lo que queda por hacer es comparar y que se realizan con la función parte libre:

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

Paso dos: funtores Clase

Este es un poco más complicado. Fácilmente podríamos detectar con SFINAE si una clase define 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); 
}; 

pero eso no nos dice si existe uno para nuestra firma de la función deseada! Afortunadamente, podemos usar un truco aquí: los punteros son parámetros de plantilla válidos. Así podemos utilizar por primera vez la función de puntero miembro de nuestra firma deseada, y comprobar si es &T::operator() de ese tipo:

template <typename T, T> struct check; 

Ahora check<void (C::*)() const, &C::operator()> sólo habrá una instanciación de plantilla válido si C en efecto una void C::operator()() const. Pero para hacer esto, primero tenemos que combinar C y la firma con un puntero de función miembro. Como ya hemos visto, tenemos que preocuparnos por dos casos adicionales que no nos deben importar para las funciones gratuitas: const y volatile funciones.Además de que es más o menos la misma:

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

Poniendo eso y nuestras conclusiones en relación con la estructura check ayudante juntos, tenemos nuestro metafunción cheque por objetos functor:

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

Tercer paso: Poner las piezas juntos

Estamos casi listos. Ahora solo tenemos que decidir cuándo usar nuestra función gratuita, y cuándo funciona el functor de clases metafunciones. Afortunadamente, C++ 11 nos proporciona un rasgo std::is_class que podemos usar para esto. Así que todo lo que tenemos que hacer es especializarse en un parámetro 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 
     > 
{}; 

por lo que finalmente puede añadir la última pieza del rompecabezas, siendo nuestro real is_callable rasgo:

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

Ahora limpiar nuestro código, ponga detalles de implementación en espacios de nombres anónimos para que no sean accesibles fuera de nuestro archivo, y tenga un buen is_callable.hpp para usar en nuestro proyecto.

código completo

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 ejemplo, con algunas pruebas

http://ideone.com/7PWdiv

+0

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

0

Aunque esto no funciona para las funciones sobrecargadas, para todos los demás casos (funciones gratuitas, clases de ejecución operator(), y lambdas) esta solución corta funciona en C++ 11:

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

Nota: std::is_callable estará disponible en C++ 17.

Cuestiones relacionadas