Pregunta

Para crear una función de plantilla de algoritmo, necesito saber si x o X (y y o Y) en la clase que es el argumento de plantilla. Puede ser útil cuando use mi función para la clase MFC CPoint o la clase GDI + PointF o algunas otras. Todos ellos usan diferentes x en ellos. Mi solución podría reducirse al siguiente código:


template<int> struct TT {typedef int type;};
template<class P> bool Check_x(P p, typename TT<sizeof(&P::x)>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<sizeof(&P::X)>::type b = 0) { return false; }

struct P1 {int x; };
struct P2 {float X; };
// it also could be struct P3 {unknown_type X; };

int main()
{
    P1 p1 = {1};
    P2 p2 = {1};

    Check_x(p1); // must return true
    Check_x(p2); // must return false

    return 0;
}

Pero no se compila en Visual Studio, mientras se compila en GNU C ++. Con Visual Studio podría usar la siguiente plantilla:


template<class P> bool Check_x(P p, typename TT<&P::x==&P::x>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<&P::X==&P::X>::type b = 0) { return false; }

Pero no se compila en GNU C ++. ¿Hay solución universal?

UPD: Las estructuras P1 y P2 aquí son solo por ejemplo. Puede haber clases con miembros desconocidos.

P.S. Por favor, no publique las soluciones de C ++ 11 aquí porque son obvias y no son relevantes para la pregunta.

¿Fue útil?

Solución

Otra forma es esta, que se basa en SFINAE para expresiones también. Si la búsqueda de nombres produce ambigüedad, el compilador rechazará la plantilla

template<typename T> struct HasX { 
    struct Fallback { int x; }; // introduce member name "x"
    struct Derived : T, Fallback { };

    template<typename C, C> struct ChT; 

    template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1]; 
    template<typename C> static char (&f(...))[2]; 

    static bool const value = sizeof(f<Derived>(0)) == 2;
}; 

struct A { int x; };
struct B { int X; };

int main() { 
    std::cout << HasX<A>::value << std::endl; // 1
    std::cout << HasX<B>::value << std::endl; // 0
}

Se basa en una idea brillante de alguien en usenet.

Nota: HasX comprueba cualquier miembro de función o datos llamado x, con un tipo arbitrario. El único propósito de introducir el nombre del miembro es tener una posible ambigüedad para la búsqueda del nombre del miembro; el tipo de miembro no es importante.

Otros consejos

Aquí hay una solución más simple que uno de Johannes Schaub - litb . Requiere C ++ 11.

#include <type_traits>

template <typename T, typename = int>
struct HasX : std::false_type { };

template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };

Actualizar : un ejemplo rápido y la explicación de cómo funciona esto.

Para estos tipos:

struct A { int x; };
struct B { int y; };

tenemos HasX < A > :: value == true y HasX < B > :: value == false . Veamos por qué.

Primero recuerde que std :: false_type y std :: true_type tienen un miembro static constexpr bool llamado value que se establece en false y true , respectivamente. Por lo tanto, las dos plantillas HasX anteriores heredan este miembro. (La primera plantilla de std :: false_type y la segunda de std :: true_type .)

Comencemos de manera simple y luego procedamos paso a paso hasta que lleguemos al código anterior.

1) Punto de partida:

template <typename T, typename U>
struct HasX : std::false_type { };

En este caso, no hay ninguna sorpresa: HasX se deriva de std :: false_type y, por tanto, HasX < bool, double > :: value == false y HasX < bool, int > :: value == false .

2) Predeterminado U :

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

Dado que U por defecto es int , Tiene < bool > en realidad significa HasX < bool, int > y por lo tanto, HasX < bool > :: value == HasX < bool, int > :: value == false .

3) Añadiendo una especialización:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX<T, int> : std::true_type { };

En general, gracias a la plantilla principal, HasX < T, U > se deriva de std :: false_type . Sin embargo, existe una especialización para U = int que se deriva de std :: true_type . Por lo tanto, HasX < bool, double > :: value == false pero HasX < bool, int > :: value == true .

Gracias al valor predeterminado para U , HasX < bool > :: value == HasX < bool, int > :: value == true .

4) decltype y una forma elegante de decir int :

Una pequeña digresión aquí, pero, por favor, tengan paciencia conmigo.

Básicamente (esto no es del todo correcto), decltype (expresión) produce el tipo de expresión . Por ejemplo, 0 tiene el tipo int , por lo tanto, decltype (0) significa int . Análogamente, 1.2 tiene el tipo doble y, por lo tanto, decltype (1.2) significa doble .

Considere una función con esta declaración:

char func(foo, int);

donde foo es un tipo de clase. Si f es un objeto de tipo foo , entonces decltype (func (f, 0)) significa char ( el tipo devuelto por func (f, 0) ).

Ahora, la expresión (1.2, 0) utiliza el operador de coma (incorporado) que evalúa las dos subexpresiones en orden (es decir, el primer 1.2 y luego 0 ), descarta el primer valor y da como resultado el segundo. Por lo tanto,

int x = (1.2, 0);

es equivalente a

int x = 0;

Poner esto junto con decltype da que decltype (1.2, 0) significa int . No hay nada realmente especial sobre 1.2 o doble aquí. Por ejemplo, true tiene el tipo bool y decltype (true, 0) también significa int .

¿Qué pasa con un tipo de clase? Por ejemplo, ¿qué significa decltype (f, 0) ? Es natural esperar que esto todavía signifique int pero puede que no sea el caso. De hecho, podría haber una sobrecarga para el operador de coma similar a la función func anterior que toma un foo y un int y devuelve un char . En este caso, decltype (foo, 0) es char .

¿Cómo podemos evitar el uso de una sobrecarga para el operador de coma? Bueno, no hay forma de sobrecargar al operador de coma para un operando void y podemos lanzar cualquier cosa a void . Por lo tanto, decltype ((void) f, 0) significa int . De hecho, (void) f lanza f de foo a void que básicamente no hace más que decir que la expresión debe se considerará que tiene el tipo void . Luego se usa la coma incorporada del operador y ((void) f, 0) da como resultado 0 que tiene el tipo int . Por lo tanto, decltype ((void) f, 0) significa int .

¿Es este elenco realmente necesario? Bueno, si no hay una sobrecarga para el operador de coma que toma foo y int , esto no es necesario. Siempre podemos inspeccionar el código fuente para ver si existe tal operador o no. Sin embargo, si esto aparece en una plantilla y f tiene el tipo V que es un parámetro de plantilla, entonces ya no está claro (o incluso es imposible saber) si dicha sobrecarga para el El operador de coma existe o no. Para ser genéricos lo echamos de todos modos.

Línea inferior: decltype ((void) f, 0) es una forma elegante de decir int .

5) SFINAE:

Esto es toda una ciencia ;-) Bien, estoy exagerando, pero tampoco es muy simple. Así que mantendré la explicación al mínimo.

SFINAE significa que la falla de sustitución no es un error. Significa que cuando un parámetro de plantilla es sustituido por un tipo, puede aparecer un código C ++ ilegal pero, en algunas circunstancias , en lugar de abortar la compilación, el compilador simplemente ignora el código ofensivo como si no estuviera allí. . Veamos cómo se aplica a nuestro caso:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };

Aquí, nuevamente, decltype ((void) T :: x, 0) es una forma elegante de decir int pero con el beneficio de SFINAE.

Cuando T se sustituye por un tipo, puede aparecer una construcción no válida. Por ejemplo, bool :: x no es válido en C ++, por lo que sustituye a T con bool en T :: x produce una construcción inválida. Bajo el principio SFINAE, el compilador no rechaza el código, simplemente lo ignora (parte de). Más precisamente, como hemos visto, HasX < bool > significa en realidad HasX < bool, int > . La especialización para U = int debe seleccionarse pero, al crear una instancia, el compilador encuentra bool :: x e ignora la especialización de la plantilla como si no existiera.

En este punto, el código es esencialmente el mismo que en el caso (2) anterior donde solo existe la plantilla principal. Por lo tanto, HasX < bool, int > :: value == false .

El mismo argumento utilizado para bool se mantiene para B , ya que B :: x es una construcción no válida ( B no tiene miembro x ). Sin embargo, A :: x está bien y el compilador no ve ningún problema en la instanciación de la especialización para U = int (o, más precisamente, para U = decltype ( (nulo) A :: x, 0) ). Por lo tanto, HasX < A > :: value == true .

6) Unnaming U :

Bueno, mirando el código en (5) de nuevo, vemos que el nombre U no se usa en ninguna parte, sino en su declaración ( typename U ). Luego podemos anular el nombre del segundo argumento de la plantilla y obtenemos el código que se muestra en la parte superior de esta publicación.

Me redireccionaron aquí desde una pregunta que tiene Se ha cerrado como duplicado de éste. Sé que es un hilo viejo, pero solo quería sugerir una implementación alternativa (¿más simple?) Que funcione con C ++ 11. Suponiendo que queremos comprobar si una determinada clase tiene una variable miembro llamada id :

#include <type_traits>

template<typename T, typename = void>
struct has_id : std::false_type { };

template<typename T>
struct has_id<T, decltype(std::declval<T>().id, void())> : std::true_type { };

Eso es todo. Y aquí es cómo se usaría ( ejemplo en vivo ) :

#include <iostream>

using namespace std;

struct X { int id; };
struct Y { int foo; };

int main()
{
    cout << boolalpha;
    cout << has_id<X>::value << endl;
    cout << has_id<Y>::value << endl;
}

Las cosas se pueden hacer aún más simples con un par de macros:

#define DEFINE_MEMBER_CHECKER(member) \
    template<typename T, typename V = bool> \
    struct has_ ## member : false_type { }; \
    template<typename T> \
    struct has_ ## member<T, \
        typename enable_if< \
            !is_same<decltype(declval<T>().member), void>::value, \
            bool \
            >::type \
        > : true_type { };

#define HAS_MEMBER(C, member) \
    has_ ## member<C>::value

Que se podría usar de esta manera:

using namespace std;

struct X { int id; };
struct Y { int foo; };

DEFINE_MEMBER_CHECKER(foo)

int main()
{
    cout << boolalpha;
    cout << HAS_MEMBER(X, foo) << endl;
    cout << HAS_MEMBER(Y, foo) << endl;
}

ACTUALIZACIÓN: Recientemente he hecho algo más con el código que publiqué en mi respuesta original, así que estoy actualizando esto para tener en cuenta los cambios / adiciones.

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

Comprueba el miembro x en una clase dada. 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;

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

Compruebe la variable miembro x:

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

Comprueba la clase de miembro x:

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

Verifica la unión miembro x:

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

Comprueba la enumeración del miembro x:

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

Compruebe si hay alguna función de 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.
*/

template <typename... Args> struct ambiguate : public Args... {};

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)

Boost.ConceptTraits proporciona, entre otras, algunas macros a defina rasgos de tipo, como por ejemplo BOOST_TT_EXT_DEFINE_HAS_MEMBER (nombre) , que define un rasgo de tipo de la forma:

has_member_##name<T>

Esto da verdadero si T tiene un tipo de miembro llamado. Sin embargo, tenga en cuenta que esto no detectará a los miembros del tipo de referencia.

En su caso, será suficiente agregar un archivo de encabezado

BOOST_TT_EXT_DEFINE_HAS_MEMBER_TYPE(x)

y verifique lo siguiente

BOOST_STATIC_ASSERT(has_member_x<P1>::value);

La técnica utilizada es la misma que se explicó en algunas de las respuestas anteriores.

Lamentablemente esta biblioteca ya no se mantiene. Ahora que C ++ 0x no incluye el concepto, esta biblioteca junto con SFINAE es un reemplazo perfecto para trabajar con la mayoría de los conceptos.

¿Por qué no utilizas una especialización como esta?

struct P1 {int x; };
struct P2 {int X; };

template<class P> 
bool Check_x(P p) { return true; }

template<> 
bool Check_x<P2>(P2 p) { return false; }

La segunda respuesta (litb) a esto muestra cómo detectar un miembro:

¿Es posible escribir una plantilla para verificar para la existencia de una función?

¿Por qué no creas las especializaciones de plantilla de Check_x?

template<> bool Check_x(P1 p) { return true; }
template<> bool Check_x(P2 p) { return false; }

Joder, cuando lo pienso. Si solo tiene dos tipos, ¿por qué necesita plantillas para esto?

¿Son las funciones (x, X, y, Y) de una clase base abstracta, o podrían refactorizarse para que sean así? Si es así, puede usar la macro SUPERSUBCLASS () de Modern C ++ Design, junto con las ideas de la respuesta a esta pregunta:

Despacho basado en el tipo de compilación

Podemos obtener en tiempo de compilación: 0 - not_member, 1 - is_object, 2 - is_function para cada clase requerida y miembro - objeto o función: http://ideone.com/Fjm9u5

#include <iostream>
#include <type_traits>

#define IS_MEMBER(T1, M)    \
struct {        \
    struct verystrangename1 { bool M; };    \
    template<typename T> struct verystrangename2 : verystrangename1, public T { }; \
    \
    enum return_t { not_member, is_object, is_function }; \
    template<typename T, typename = decltype(verystrangename2<T>::M)> constexpr return_t what_member() { return not_member;  }  \
    template<typename T> typename std::enable_if<std::is_member_object_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() { return is_object; }   \
    template<typename T> typename std::enable_if<std::is_member_function_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() { return is_function; }   \
    constexpr operator return_t() { return what_member<T1>(); } \
}

struct t {
    int aaa;
    float bbb;
    void func() {}
};

// Can't be in function
IS_MEMBER(t, aaa) is_aaa_member_of_t;
IS_MEMBER(t, ccc) is_ccc_member_of_t;
IS_MEMBER(t, func) is_func_member_of_t;

// known at compile time
enum { const_is_aaa_member_of_t = (int)is_aaa_member_of_t };
static constexpr int const_is_func_member_of_t = is_func_member_of_t;

int main() {        
    std::cout << std::boolalpha << "0 - not_member, 1 - is_object, 2 - is_function \n\n" <<
        "is aaa member of t = " << is_aaa_member_of_t << std::endl << 
        "is ccc member of t = " << is_ccc_member_of_t << std::endl << 
        "is func member of t = " << is_func_member_of_t << std::endl << 
        std::endl;

    return 0;
}

Resultado:

0 - not_member, 1 - is_object, 2 - is_function 

is aaa member of t = 1
is ccc member of t = 0
is func member of t = 2

Para class / struct:

struct t {
    int aaa;
    float bbb;
    void func() {}
};
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top