¿Cómo detectar si hay una variable miembro específica en la clase?
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 la plantilla. Puede resultar útil cuando uso 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 que 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++. ¿Existe una solución universal?
UPD: Las estructuras P1 y P2 aquí son solo a modo de ejemplo. Podría haber clases con miembros desconocidos.
PD: Por favor, no publique soluciones de C++ 11 aquí porque son obvias y no relevantes para la pregunta.
He aquí una solución más sencilla que la 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 { };
Actualización : un ejemplo rápido y una explicación de cómo funciona.
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 eso std::false_type
y std::true_type
tenga un static constexpr bool
miembro nombrado value
que esté configurado 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 avancemos paso a paso hasta llegar al código anterior.
1) Punto de partida:
template <typename T, typename U>
struct HasX : std::false_type { };
En este caso, no hay sorpresa: HasX
deriva de std::false_type
y por tanto HasX<bool, double>::value == false
y HasX<bool, int>::value == false
.
2) Incumplimiento U
:
// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };
Dado que U
el valor predeterminado es int
, Has<bool>
en realidad significa HasX<bool, int>
y, por lo tanto HasX<bool>::value == HasX<bool, int>::value == false
,.
3) Agregar 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 primaria, HasX<T, U>
se deriva de std::false_type
. Sin embargo, existe una especialización que U = int
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.
Básicamente (esto no es del todo correcto), decltype(expression)
produce el tipo de expresión . Por ejemplo, 0
tiene tipo, int
por lo tanto, decltype(0)
significa int
. De manera análoga, 1.2
tiene tipo double
y por tanto, decltype(1.2)
significa double
.
Considere una función con esta declaración:
char func(foo, int);
¿ Dónde foo
hay algún 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 (integrado) que evalúa las dos subexpresiones en orden (es decir, primero 1.2
y luego 0
), descarta el primer valor y da como resultado el segundo. Por eso,
int x = (1.2, 0);
es equivalente a
int x = 0;
Poner esto junto con decltype
da eso decltype(1.2, 0)
significa int
. No hay nada realmente especial sobre 1.2
o double
aquí. Por ejemplo, también true
tiene tipo bool
y decltype(true, 0)
medio .int
¿Qué pasa con un tipo de clase? Por ejemplo, ¿qué decltype(f, 0)
significa? Es natural esperar que esto siga significando 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 a foo
y an int
y devuelve a 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 manera de sobrecargar el operador de coma para un void
operando y podemos convertir cualquier cosa a void
. Por lo tanto, decltype((void) f, 0)
significa int
. De hecho, (void) f
las conversiones f
from foo
to void
básicamente no hacen más que decir que se debe considerar que la expresión tiene tipo void
. Luego se utiliza la coma del operador incorporado y ((void) f, 0)
el resultado es 0
que tiene tipo int
. Por lo tanto, decltype((void) f, 0)
significa int
.
¿Es realmente necesario este elenco? Bueno, si no hay sobrecarga para el operador de coma foo
, int
entonces esto no es necesario. Siempre podemos inspeccionar el código fuente para ver si existe dicho operador o no. Sin embargo, si esto aparece en una plantilla y f
tiene un tipo V
que es un parámetro de plantilla, entonces ya no está claro (o incluso es imposible saberlo) si dicha sobrecarga para el operador de coma existe o no. Para ser genéricos, lanzamos de todos modos.
En pocas palabras: decltype((void) f, 0)
es una forma elegante de decir int
.
5) SFINAE:
Esto es toda una ciencia ;-) Vale, estoy exagerando pero tampoco es muy sencillo. 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 se sustituye 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 infractor 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)
hay una forma elegante de decirlo 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
C++ no es válido, por lo que sustituirlo T
con bool
in T::x
produce una construcción no válida. Según el principio SFINAE, el compilador no rechaza el código, simplemente lo ignora (partes de él). Más precisamente, como hemos visto HasX<bool>
significa en realidad HasX<bool, int>
. U = int
Se debe seleccionar la especialización para pero, al crear una instancia, el compilador encuentra bool::x
e ignora la especialización de la plantilla por completo 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 eso, HasX<bool, int>::value == false
.
El mismo argumento utilizado para bool
se cumple B
desde 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 crear una instancia de la especialización para U = int
(o, más precisamente, para U = decltype((void) A::x, 0)
). Por eso, HasX<A>::value == true
.
6) Sin nombre U
:
Bueno, mirando el código en (5) nuevamente, vemos que el nombre U
no se usa en ningún otro lugar excepto en su declaración ( typename U
). Luego podemos quitarle el nombre al segundo argumento de la plantilla y obtenemos el código que se muestra en la parte superior de esta publicación.
Otra forma es ésta, que también se basa en SFINAE para las expresiones . Si la búsqueda de nombres resulta 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 busca cualquier dato o miembro de función llamado x, con tipo arbitrario. El único propósito de introducir el nombre del miembro es generar una posible ambigüedad en la búsqueda del nombre del miembro; el tipo de miembro no es importante.
Me redirigieron aquí desde una pregunta que se cerró como un duplicado de esta. Sé que es un hilo antiguo, pero solo quería sugerir una implementación alternativa (¿más simple?) que funcione con C++ 11. Supongamos 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 así es como 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 simplificar aún más 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 podría usarse 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;
}