Pregunta

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.htmpero no lo 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 se compilará.En mi problema necesito hacer algo si una clase tiene esa función, en caso contrario hacer "algo más".

boost::serialization enfrentó un problema similar pero no me gusta la solución que adoptaron:una función de plantilla que invoca de forma predeterminada 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 determinado) con una firma particular, de lo contrario ocurrirá un error de compilación.Se trata de implementar una serialización tanto intrusiva como 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 el espacio de nombres boost::serialization, de modo que tenga EN SU CÓDIGO DE CLIENTE para abrir el impulso del espacio de nombres y la serialización del espacio de nombres.
  2. La pila para resolver ese desorden fue de 10 a 12 invocaciones de funciones.

Necesito definir un comportamiento personalizado para 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)

¿Puedes darme una pista para resolver este rompecabezas?

¿Fue útil?

Solución

No estoy seguro de haberlo entendido correctamente, pero puede aprovechar 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>());
}

Otros consejos

A continuación se muestra una posible implementación que se basa 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 observa Mike Kinghan 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

La respuesta aceptada a esta pregunta de la introspección de la función de miembro de la compilación, aunque es justamente popular, tiene un enganche 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, el programa genera 110 - informándonos queT = std::shared_ptr<int> hace no proporcionar int & T::operator*() const.

Si aún no conoce este problema, entonces eche un vistazo a la definición destd::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.Entonces la instanciación de la plantillaSFINAE<U, &U::operator*> eso constituye "encontrar" al operador paraU = std::shared_ptr<T> no sucederá, porque std::shared_ptr<T> no tieneoperator*() Por el derecho y la instanciación de la plantilla no "herencia".

Este inconveniente no afecta el conocido enfoque SFINAE, utilizando "el truco sizeOf ()", para detectar simplemente si T tiene alguna función miembro mf (ver por ej.esta respuesta y comentarios).Pero estableciendo eso T::mf existe a menudo (¿normalmente?) no es lo suficientemente bueno:También es posible que deba establecer que tiene una firma deseada.Ahí es donde los puntajes de la técnica ilustrada.La variante puntatinizada de la firma deseada se inscribe en un parámetro de un tipo de plantilla que debe satisfacerse con&T::mf para que la investigación SFINAE tenga éxito.Pero esta técnica de instancia de plantilla da la respuesta incorrecta cuando T::mf se hereda.

Una técnica SFINAE segura para la introspección en tiempo de compilación de T::mf debe evitar el uso de &T::mf Dentro de un argumento de plantilla para instanciar un tipo sobre el cual depende la resolución de la plantilla de función SFINAE.En cambio, la resolución de la función de la plantilla de SFINAE puede depender solo de las declaraciones de tipo de tipo exactamente pertinentes utilizadas como tipos de argumento de la función de sonda SFINAE sobrecargada.

A través de una respuesta a la pregunta que permanece por esta restricción, ilustraré para la detección de compiletización de E T::operator*() const, para arbitrario T y E.Se aplicará el mismo patrón. mutatis mutandispara 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() está "invocado recursivamente".(Por supuesto, en realidad no se invoca en absoluto;Simplemente tiene los tipos de retorno de invocaciones hipotéticas resueltas por el compilador).

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

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

Obtenemos las respuestas evaluando el tipo de devolución de una sola llamada a test(0,0).Eso se hace por:

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

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

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

Este llamado a test(0,0) resolverá /* SFINAE operator-exists :) */ En caso de que el primer argumento 0 satifica el primer tipo de parámetro de esa sobrecarga, que es decltype(&A::operator*), con A = T.0 satisfará ese tipo por si acaso T::operator* existe.

Supongamos que el compilador dice sí a eso.Entonces va con/* 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 devolución de otra llamada a test().

Esta vez, pasamos solo un argumento, &A::operator*, que ahora sabemos que existe, o no estaríamos aquí.una llamada a test(&A::operator*) podría resolver o /* SFINAE operator-has-correct-sig :) */ o nuevamente para poder resolver /* SFINAE game over :( */.La llamada coincidirá/* SFINAE operator-has-correct-sig :) */ por si acaso &A::operator* Satisface 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 tiene que evaluar el tipo de retorno de la sobrecarga.No más "recursiones" ahora: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 devolución 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 que una vez más caiga al enganche que evita?

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

Verificar miembro x en 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;

Verificar la función 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;

Comprobar la variable miembro x:

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

Consultar clase de miembro x:

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

Verifique el sindicato miembro 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 miembro x independientemente 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)

Esto debería ser suficiente si conoce el nombre de la función miembro que espera.(En este caso, la función bla no puede crear una instancia si no hay una función miembro (escribir una que funcione de todos modos es difícil porque falta una especialización parcial de la función).Es posible que necesite usar plantillas de clase). Además, la estructura enable (que es similar a enable_if) también podría tener una plantilla según 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);
}

Aquí hay una versión más simple de la respuesta de Mike Kinghan.Esto detectará métodos heredados.También comprobará la exacto firma (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, "");

Ejecutable ejemplo

Puedes usar std::is_member_function_pointer

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

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

Yo mismo tuve el mismo tipo de problema y las soluciones propuestas aquí me parecieron muy interesantes...pero tenía el requisito de una solución que:

  1. También detecta funciones heredadas;
  2. Es compatible con compiladores que no están preparados para C++ 11 (por lo que no hay decltype)

encontré otro hilo proponer algo como esto, basado en una IMPULSAR la discusión.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 impulso::tiene_* clases.

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

Para lograr esto necesitaremos usar:

  1. Sobrecarga de plantilla de funciones con diferentes tipos de devolución según si el método está disponible
  2. De acuerdo con los metacondicionales en el type_traits encabezado, querremos devolver un true_type o false_type de nuestras sobrecargas
  3. Declarar el true_type sobrecarga esperando una int y el false_type sobrecarga esperando que se exploten los parámetros variables: "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 la plantilla para el true_type función que usaremos declval y decltype permitiéndonos 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 te lo explico a continuación:

Quiero verificar la existencia de una función llamada test que toma un tipo convertible de 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))::value es true (Tenga en cuenta que no es necesario crear una funcionalidad especial para lidiar con el void a::test() sobrecarga, el void a::test(int) es aceptado)
  • decltype(hasTest<b>(0))::value es true (Porque int es convertible a double int b::test(double) se acepta, independientemente del tipo de devolución)
  • decltype(hasTest<c>(0))::value es false (c no tiene un método llamado test que acepta un tipo convertible de int por lo tanto esto no es aceptado)

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, especialmente si queremos probar nombres similares, por ejemplo, ¿cómo llamarí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 van a usar con una clase, esa clase debe 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>::value o details::test_void<a>::value cedería true o false para fines de código en línea o metaprogramación.

Para no ser intrusivo, también puedes poner serialize en el espacio de nombres de la clase que se está serializando, o de la clase de archivo, gracias a búsqueda de koenig.Ver Espacios de nombres para anulaciones de funciones gratuitas para más detalles.:-)

Abrir cualquier espacio de nombres determinado para implementar una función gratuita es simplemente incorrecto.(por ejemplo, se supone que no debes abrir el espacio de nombres std para implementar swap para sus propios tipos, pero debería utilizar la búsqueda de Koenig en su lugar).

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

El artículo de Herb Sutter habla de 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 de modo que no implique mucho copiar y pegar.

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

Sin soporte 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 espero que funcione

A, Aa y B son las clases en cuestión, Aa siendo el especial el que hereda el miembro que buscamos.

En el FooFinder el true_type y false_type son los reemplazos de las clases correspondientes de C++ 11.También para comprender la metaprogramación de plantillas, revelan la base misma del truco del tamaño de SFINAE.

El TypeSink es una estructura de plantilla que se utiliza más adelante para hundir el resultado integral de la sizeof operador en una creación de instancias de plantilla para formar un tipo.

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

Ambos test Las funciones junto con la declaración enum finalmente forman el patrón central SFINAE.Hay uno genérico que usa puntos suspensivos que devuelve el false_type y una contraparte con argumentos más específicos que deben tener prioridad.

Para poder instanciar el test función con un argumento de plantilla de T, el match Se debe crear una instancia de la función, ya que se requiere su tipo de retorno para crear una instancia del TypeSink argumento.La advertencia es que &U::foo, al estar envuelto en un argumento de función, es no se hace referencia desde dentro de una especialización de argumento de plantilla, por lo que la búsqueda de miembros heredados aún se lleva a cabo.

Creo que la respuesta que estás buscando está aquí.

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

y un ejemplo un poco más completo aquí

http://pastie.org/298994

Utilizo la técnica para detectar la presencia de un soporte. operador ostream << en la clase en cuestión y luego generar un fragmento de código diferente dependiendo.

No creía que fuera posible antes de encontrar la solución vinculada, pero es un truco muy interesante.Dedique tiempo a comprender el código y valdrá la pena.

Puntilla

Si estás utilizando Facebook Folly, hay una macro lista para usar que te ayudará:

#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 los de la respuesta anterior, usar una biblioteca es más simple.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top