Compruebe si una clase tiene una función miembro de una firma determinada

135

Estoy pidiendo un truco de plantilla para detectar si una clase tiene una función miembro específica de una firma determinada.

El problema es similar al citado aquí http://www.gotw.ca/gotw/071.htm pero no el mismo: en el artículo del libro de Sutter respondió a la pregunta de que una clase C DEBE PROPORCIONAR una función miembro con una firma particular, de lo contrario el programa no compilará. En mi problema, necesito hacer algo si una clase tiene esa función, de lo contrario hacer "otra cosa".

El problema :: serialización se enfrentó a un problema similar, pero no me gusta la solución que adoptaron: una función de plantilla que invoca por defecto una función libre (que debe definir) con una firma particular a menos que defina una función miembro particular ( en su caso "serializar" que toma 2 parámetros de un tipo dado) con una firma particular, de lo contrario se producirá un error de compilación. Esto es para implementar la serialización intrusiva y no intrusiva.

No me gusta esa solución por dos razones:

  1. Para no ser intrusivo, debe anular la función global "serializar" que se encuentra en boost :: serialization namespace, por lo que tiene EN SU CÓDIGO DE CLIENTE para abrir el refuerzo de espacio de nombres y la serialización de espacios de nombres.
  2. La pila para resolver ese desastre fue de 10 a 12 invocaciones de funciones.

Necesito definir un comportamiento personalizado para las clases que no tienen esa función miembro, y mis entidades están dentro de diferentes espacios de nombres (y no quiero anular una función global definida en un espacio de nombres mientras estoy en otro)

¿Me puede dar una pista para resolver este rompecabezas?

ugasoft
fuente
1
Pregunta similar: stackoverflow.com/questions/257288
Johannes Schaub - litb
@ R.MartinhoFernandes ¿Qué tipo de respuesta estás buscando? Esta respuesta de Mike Kinghan va bastante en profundidad y está usando cosas de C ++ 11.
jrok
@ R.MartinhoFernandes ¿Quizás esta es la versión moderna que estás buscando?
Daniel Frey

Respuestas:

90

No estoy seguro si te entiendo correctamente, pero puedes explotar SFINAE para detectar la presencia de funciones en tiempo de compilación. Ejemplo de mi código (prueba si la clase tiene una función miembro 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>());
}
yrp
fuente
14
¿¿¿que es esto??? ¿Es legal el código C ++? ¿puedes escribir "template <typename U, size_t (U :: *) () const>" ?? pero ... ¡es una excelente y nueva solución! Gracias, mañana analizaré mejor con mis colegas ... ¡genial!
ugasoft
2
Al ejemplo le falta la definición de 'int_to_type'. Obviamente, no se agrega a la respuesta, pero sí significa que las personas pueden ver su código en acción después de un corte y pegado rápido.
Richard Corden
2
Una definición simple de int_to_type podría ser: 'template <int N> struct int_to_type {};'. Muchas implementaciones mantienen el valor del parámetro N en una enumeración o bien en una constante entera estática (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
Simplemente tome boost :: integral_constant en lugar de int_to_type.
Vadim Ferderer
2
@JohanLundberg Es una función miembro de puntero a (no estático). Por ejemplo, size_t(std::vector::*p)() = &std::vector::size;.
Vuelva a instalar Mónica
133

Aquí hay una posible implementación basada en las características de C ++ 11. Detecta correctamente la función incluso si se hereda (a diferencia de la solución en la respuesta aceptada, como Mike Kinghan observa en su respuesta ).

La función que prueba este fragmento se llama 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
jrok
fuente
¿Funciona esto si Y no tiene un método llamado "serializar"? No veo cómo devolvería un valor falso si el método "serializar" no existiera.
Collin
1
@Collin en ese caso, la sustitución del parámetro de plantilla falla para la primera sobrecarga de la comprobación y se descarta del conjunto de sobrecarga. Vuelve al segundo que devuelve false_type. Esto no es un error del compilador debido al principio SFINAE.
jrok
1
@ elios264 No hay. Puede usar una macro para escribir una plantilla para cada función que desee verificar.
jrok
1
¿Alguna razón particular por la cual el argumento para la verificación es de tipo T * en lugar de T o T &?
shibumi el
1
Pero qué pasa si el serializemismo acepta una plantilla. ¿Hay alguna forma de comprobar la serializeexistencia sin escribir el tipo exacto?
Hola Ángel,
37

La respuesta aceptada a esta pregunta de la introspección de la función miembro de compiletime, aunque es bastante popular, tiene un inconveniente que se puede observar en el siguiente programa:

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

Construido con GCC 4.6.3, los productos del programa 110- para informarnos que T = std::shared_ptr<int>hace no proporcionan int & T::operator*() const.

Si aún no es sabio con este problema, una mirada a la definición de std::shared_ptr<T>en el encabezado <memory>arrojará luz. En esa implementación, std::shared_ptr<T>se deriva de una clase base de la que hereda operator*() const. Por lo tanto, la creación de instancias de plantilla SFINAE<U, &U::operator*>que constituye "encontrar" el operador para U = std::shared_ptr<T>no ocurrirá, porque std::shared_ptr<T>no tiene operator*()por derecho propio y la creación de instancias de plantilla no "hace herencia".

Este inconveniente no afecta el conocido enfoque SFINAE, que utiliza "El truco sizeof ()", para detectar simplemente si Ttiene alguna función miembro mf(ver, por ejemplo, esta respuesta y comentarios). Pero establecer que T::mfexiste a menudo (¿generalmente?) No es lo suficientemente bueno: es posible que también deba establecer que tiene la firma deseada. Ahí es donde puntúa la técnica ilustrada. La variante punteada de la firma deseada se inscribe en un parámetro de un tipo de plantilla que debe ser satisfecho &T::mfpara que la sonda SFINAE tenga éxito. Pero esta técnica de creación de instancias de plantilla da la respuesta incorrecta cuando T::mfse hereda.

Una técnica segura de SFINAE para la introspección en tiempo de compilación T::mfdebe evitar el uso &T::mfdentro de un argumento de plantilla para crear una instancia de un tipo del que depende la resolución de la plantilla de función SFINAE. En cambio, la resolución de la función de plantilla SFINAE puede depender solo de las declaraciones de tipo exactamente pertinentes utilizadas como tipos de argumento de la función de sonda SFINAE sobrecargada.

A modo de respuesta a la pregunta que cumple con esta restricción, ilustraré la detección en tiempo de compilación de E T::operator*() const, para arbitraria Ty E. El mismo patrón se aplicará mutatis mutandis para buscar cualquier otra firma de método miembro.

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

En esta solución, la función de sonda SFINAE sobrecargada test()se "invoca de forma recursiva". (Por supuesto, en realidad no se invoca en absoluto; simplemente tiene los tipos de retorno de invocaciones hipotéticas resueltas por el compilador).

Necesitamos investigar al menos uno y como máximo dos puntos de información:

  • ¿Existe T::operator*()en absoluto? Si no, hemos terminado.
  • Dado que T::operator*()existe, ¿es su firma E T::operator*() const?

Obtenemos las respuestas evaluando el tipo de retorno de una sola llamada a test(0,0). Eso lo hace:

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

Esta llamada puede resolverse a la /* SFINAE operator-exists :) */sobrecarga de test(), o puede resolverse a la /* SFINAE game over :( */sobrecarga. No se puede resolver la /* SFINAE operator-has-correct-sig :) */sobrecarga, porque uno espera solo un argumento y estamos pasando dos.

¿Por qué estamos pasando dos? Simplemente para forzar la resolución a excluir /* SFINAE operator-has-correct-sig :) */. El segundo argumento no tiene otro significado.

Esta llamada a test(0,0)se resolverá /* SFINAE operator-exists :) */en caso de que el primer argumento 0 satifique el primer tipo de parámetro de esa sobrecarga, que es decltype(&A::operator*), con A = T. 0 satisfará ese tipo en caso de que T::operator*exista.

Supongamos que el compilador dice Sí a eso. Luego continúa /* SFINAE operator-exists :) */y necesita determinar el tipo de retorno de la llamada de función, que en ese caso es decltype(test(&A::operator*)): el tipo de retorno de otra llamada más test().

Esta vez, estamos pasando un solo argumento, &A::operator*que ahora sabemos que existe, o no estaríamos aquí. Una llamada a test(&A::operator*)podría resolverse ao /* SFINAE operator-has-correct-sig :) */nuevamente o podría resolverse a /* SFINAE game over :( */. La llamada coincidirá /* SFINAE operator-has-correct-sig :) */en caso de que &A::operator*satisfaga el tipo de parámetro único de esa sobrecarga, que es E (A::*)() const, con A = T.

El compilador dirá Sí aquí si T::operator*tiene esa firma deseada, y luego nuevamente tendrá que evaluar el tipo de retorno de la sobrecarga. No más "recurrencias" ahora: lo es std::true_type.

Si el compilador no elige /* SFINAE operator-exists :) */para la llamada test(0,0)o no elige /* SFINAE operator-has-correct-sig :) */ para la llamada test(&A::operator*), entonces en cualquier caso va con /* SFINAE game over :( */y el tipo de retorno final es std::false_type.

Aquí hay un programa de prueba que muestra la plantilla que produce las respuestas esperadas en una muestra variada de casos (GCC 4.6.3 nuevamente).

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

¿Hay nuevos defectos en esta idea? ¿Se puede hacer más genérico sin volver a caer en el obstáculo que evita?

Mike Kinghan
fuente
16

Aquí hay algunos fragmentos de uso: * Las agallas para todo esto están más abajo

Verifica si hay un miembro xen una clase determinada. Podría ser var, func, class, union o enum:

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

Verifique la función del miembro 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;

Verifique la variable miembro x:

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

Verifique la clase de miembro x:

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

Verifique la unión de miembros x:

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

Verifique la enumeración de miembros x:

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

Verifique cualquier función de miembro xindependientemente de la 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;

Detalles y núcleo:

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

Macros (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)
Brett Rossier
fuente
1
Esto es genial; Sería bueno poner esto en una biblioteca de archivos de encabezado único.
Allan
12

Esto debería ser suficiente si conoce el nombre de la función miembro que espera. (En este caso, la función bla no se puede crear una instancia si no hay una función miembro (escribir una que funcione de todos modos es difícil porque hay una falta de especialización parcial de la función. Es posible que necesite usar plantillas de clase) Además, la estructura de habilitación (que es similar a enable_if) también se podría incluir en el tipo de función que desea que tenga como miembro.

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);
}
coppro
fuente
44
gracias! es similar a la solución propuesta por yrp. No sabía que la plantilla puede ser modulada sobre las funciones de los miembros. ¡Esa es una nueva característica que he aprendido hoy! ... y una nueva lección: "nunca digas que eres experto en c ++" :)
ugasoft
7

Aquí hay una versión más simple de la respuesta de Mike Kinghan. Esto detectará métodos heredados. También verificará la firma exacta (a diferencia del enfoque de jrok que permite conversiones de argumentos).

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

Ejemplo ejecutable

Valentin Milea
fuente
Esto es bueno, pero no funcionará si la función no tiene argumento
Triskeldeian
Funciona muy bien. No tuve ningún problema para aplicar este truco a las funciones de miembros sin tomar argumentos.
JohnB
Esto funciona bien para mí con argumentos de métodos múltiples y sin método, incluso con sobrecargas, e incluso con herencia, y con el uso de usingtraer sobrecargas de la clase base. A mí me funciona en MSVC 2015 y con Clang-CL. Sin embargo, no funciona con MSVC 2012.
steveire
5

Puede usar std :: is_member_function_pointer

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

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
Yochai Timmer
fuente
16
¿No &A::foohabrá un error de compilación si no hay nada fooen absoluto A? Leí la pregunta original porque se supone que funciona con cualquier clase de entrada, no solo aquellas que tienen algún tipo de miembro nombrado foo.
Jeff Walden
5

Llegué con el mismo tipo de problema, y ​​encontré las soluciones propuestas aquí muy interesantes ... pero tenía el requisito de una solución que:

  1. Detecta funciones heredadas también;
  2. Es compatible con compiladores que no son compatibles con C ++ 11 (por lo que no hay tipo de declty)

Encontré otro hilo proponiendo algo como esto, basado en una discusión de BOOST . Aquí está la generalización de la solución propuesta como declaración de dos macros para la clase de rasgos, siguiendo el modelo de las clases 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__

Estas macros se expanden a una clase de rasgos con el siguiente 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;
};

Entonces, ¿cuál es el uso típico que se puede hacer con esto?

// 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)
}
S. Paris
fuente
5

Para lograr esto, necesitaremos usar:

  1. Sobrecarga de plantilla de función con diferentes tipos de retorno según si el método está disponible
  2. De acuerdo con los metacondicionales en el type_traitsencabezado, querremos devolver true_typeaofalse_type de nuestras sobrecargas
  3. Declare la true_typesobrecarga que espera una inty la false_typesobrecarga que espera que los Parámetros Variados exploten: "La prioridad más baja de la conversión de puntos suspensivos en la resolución de sobrecarga"
  4. Al definir la especificación de plantilla para la true_typefunción que usaremos declvaly decltypenos permitirá detectar la función independientemente de las diferencias de tipo de retorno o sobrecargas entre métodos

Puedes ver un ejemplo en vivo de esto aquí . Pero también lo explicaré a continuación:

Quiero verificar la existencia de una función llamada testque toma un tipo convertible int, entonces necesitaría declarar estas dos funciones:

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))::valuees true(Tenga en cuenta que no es necesario crear una funcionalidad especial para hacer frente a la void a::test()sobrecarga, void a::test(int)se acepta)
  • decltype(hasTest<b>(0))::valuees true(porque intes convertible a double int b::test(double)es aceptado, independiente del tipo de retorno)
  • decltype(hasTest<c>(0))::valuees false( cno tiene un método llamado testque acepte un tipo convertible a partir de intahí, por lo que esto no se acepta)

Esta solución tiene 2 inconvenientes:

  1. Requiere una declaración por método de un par de funciones
  2. Crea contaminación del espacio de nombres, particularmente si queremos probar nombres similares, por ejemplo, ¿qué nombre le daríamos a una función que quisiera probar un test()método?

Por lo tanto, es importante que estas funciones se declaren en un espacio de nombres de detalles, o idealmente si solo se usan con una clase, esa clase debería declararlas de forma privada. Con ese fin, he escrito una macro para ayudarlo a resumir esta información:

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

Podrías usar esto como:

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

Posteriormente llamando details::test_int<a>::valueo details::test_void<a>::valuecedería trueo falsecon el propósito de código en línea o meta-programación.

Jonathan Mee
fuente
3

Para no ser intrusivo, también puede poner serializeen el espacio de nombres de la clase que se está serializando, o de la clase de archivo, gracias a la búsqueda de Koenig . Consulte Espacios de nombres para anulaciones de funciones gratuitas para obtener más detalles. :-)

Abrir cualquier espacio de nombres dado para implementar una función gratuita es simplemente incorrecto. (por ejemplo, se supone que no debe abrir el espacio stdde nombres para implementar swapsus propios tipos, sino que debe usar la búsqueda de Koenig en su lugar).

Chris Jester-Young
fuente
3

Parece que quieres el idioma del detector. Las respuestas anteriores son variaciones de esto que funcionan con C ++ 11 o C ++ 14.

La std::experimentalbiblioteca tiene características que hacen esencialmente esto. Reelaborando un ejemplo de arriba, podría ser:

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

Si no puede usar std :: experimental, se puede hacer una versión rudimentaria como esta:

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;

Como has_serialize_t es realmente std :: true_type o std :: false_type, se puede usar a través de cualquiera de los modismos comunes de SFINAE:

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

O mediante el envío con resolución de sobrecarga:

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);
}
lar
fuente
2

Bueno. Segundo intento. Está bien si tampoco te gusta este, estoy buscando más ideas.

El artículo de Herb Sutter habla sobre rasgos. Por lo tanto, puede tener una clase de rasgos cuya instanciación predeterminada tenga el comportamiento alternativo, y para cada clase donde exista su función miembro, entonces la clase de rasgos está especializada para invocar la función miembro. Creo que el artículo de Herb menciona una técnica para hacer esto para que no implique muchas copias y pegados.

Sin embargo, como dije, tal vez no desee el trabajo adicional involucrado con las clases de "etiquetado" que implementan ese miembro. En cuyo caso, estoy buscando una tercera solución ...

Chris Jester-Young
fuente
eh ... He analizado esta solución ... Creo que es un poco demasiado caro para los usuarios de mi marco. (ok, lo admito, estoy desarrollando un marco de transmisión y
elijo
Mi tercera solución sería usar SFINAE. Dado que la respuesta de yrp ya lo menciona, no lo abordaré (porque todavía estoy investigando sobre ello: conozco la idea, pero el diablo está en los detalles), a menos que su solución no funcione para ti al final . :-)
Chris Jester-Young
1

Sin el soporte de C ++ 11 ( decltype) esto podría funcionar:

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

¿Cómo funciona?

A, Aay Bson las clases en cuestión,Aa siendo la especial que hereda el miembro que estamos buscando.

En el FooFinderla true_typey false_typeson los reemplazos para el corresponsal C ++ 11 clases. También para la comprensión de la meta programación de plantillas, revelan la base misma del truco del tamaño de SFINAE.

El TypeSinkes una estructura de plantilla que se utiliza más tarde para hundir el resultado integral del sizeofoperador en una instanciación de plantilla para formar un tipo.

La matchfunción es otro tipo de plantilla SFINAE que se deja sin una contraparte genérica. Por lo tanto, solo se puede crear una instancia si el tipo de su argumento coincide con el tipo para el que estaba especializado.

Ambas testfunciones junto con la declaración enum finalmente forman el patrón central SFINAE. Hay uno genérico que usa puntos suspensivos que devuelve elfalse_type y una contraparte con argumentos más específicos para tener prioridad.

Para poder instanciar la testfunción con un argumento de plantilla T, la matchfunción debe ser instanciada, ya que se requiere su tipo de retorno para instanciar el TypeSinkargumento. La advertencia es que &U::foo, al estar envuelto en un argumento de función, no se hace referencia desde dentro de una especialización de argumento de plantilla, por lo que la búsqueda de miembros heredados todavía tiene lugar.

Kamajii
fuente
1

Si está utilizando la locura de Facebook, su macro lista para usar le ayudará a:

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

Aunque los detalles de implementación son los mismos que en la respuesta anterior, usar una biblioteca es más simple.

prehistoricpenguin
fuente
0

Tenía una necesidad similar y me encontré con este SO. Aquí se proponen muchas soluciones interesantes / poderosas, aunque es un poco largo para una necesidad específica: detectar si una clase tiene una función miembro con una firma precisa. Así que leí / probé y obtuve mi versión que podría ser de interés. Detecta:

  • función miembro estática
  • función miembro no estática
  • función miembro no estática const

Con una firma precisa. Como no necesito capturar ninguna firma (eso requeriría una solución más complicada), esta es una opción para mí. Básicamente utilizaba 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;
}

Salida:

           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
ctNGUYEN
fuente
0

Sobre la base de JROK 's respuesta , he evitado el uso de clases y / o funciones de plantilla anidadas.

#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

Podemos usar las macros anteriores de la siguiente manera:

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

Las sugerencias son bienvenidas.

debashish.ghosh
fuente