¿Cómo detectar la existencia de una clase usando SFINAE?

¿Es posible detectar si existe una clase en C ++ con SFINAE ? Si es posible, ¿cómo?

Supongamos que tenemos una clase proporcionada solo por algunas versiones de una biblioteca. Me gustaría saber si es posible usar SFINAE para detectar si la clase existe o no. El resultado de la detección es arbitrario, digamos una constante enum que es 1 si existe, 0 en caso contrario.

Si le pedimos al comstackdor que nos diga algo acerca de un tipo de clase T que no ha sido declarado, tendremos que obtener un error de comstackción. No hay forma de evitar eso. Por lo tanto, si queremos saber si la clase T “existe”, donde T podría no haberse declarado aún, primero debemos declarar T

Pero eso está bien, porque el simple hecho de declarar T no lo hará “existir”, ya que lo que debemos entender por T existe es que T se define . Y si, después de haber declarado T , puede determinar si ya está definido , no necesita estar en ninguna confusión.

Entonces, el problema es determinar si T es un tipo de clase definido.

sizeof(T) no es de ayuda aquí. Si T no está definido, dará un error incomplete type T . Del typeid(T) modo typeid(T) . Tampoco es bueno crear una sonda SFINAE en el tipo T * , porque T * es un tipo definido siempre que T haya sido declarado, incluso si T no lo es. Y dado que estamos obligados a tener una statement de clase T , std::is_class tampoco es la respuesta, porque esa statement será suficiente para que diga “Sí”.

C ++ 11 proporciona std::is_constructible en . ¿Puede esto ofrecer una solución fuera de serie? – dado que si se define T , entonces debe tener al menos un constructor.

No estoy fracado. Si conoce la firma de al menos un constructor público de T entonces los (a partir de 4.6.3) sí lo harán. Digamos que un constructor público conocido es T::T(int) . Entonces:

 std::is_constructible::value 

será verdadero si T es definido y falso si T es meramente declarado.

Pero esto no es portátil. en VC ++ 2010 aún no proporciona std::is_constructible e incluso std::has_trivial_constructor borrará si T no está definido: lo más probable es que cuando std::is_constructible llegue siga su ejemplo. Además, en la eventualidad de que solo los constructores privados de T existan para ofrecer std::is_constructible , incluso GCC descartará (lo cual es una provocación).

Si se define T , debe tener un destructor y solo un destructor. Y ese destructor es más probable que sea público que cualquier otro miembro posible de T En esa luz, la jugada más simple y más fuerte que podemos hacer es crear una sonda SFINAE para la existencia de T::~T

Esta sonda SFINAE no puede elaborarse de forma rutinaria para determinar si T tiene una función miembro ordinaria mf , lo que hace que la “sobrecarga Sí” de la función sonda SFINAE tome un argumento definido en términos del tipo de &T::mf . Porque no podemos tomar la dirección de un destructor (o constructor).

Sin embargo, si T se define, entonces T::~T tiene un tipo DT – que debe ser cedido por decltype(dt) siempre que dt sea ​​una expresión que evalúe a una invocación de T::~T ; y, por lo tanto, DT * también será un tipo, que en principio se puede dar como el tipo de argumento de una sobrecarga de función. Por lo tanto, podemos escribir la sonda de esta manera (GCC 4.6.3):

 #ifndef HAS_DESTRUCTOR_H #define HAS_DESTRUCTOR_H #include  /*! The template `has_destructor` exports a boolean constant `value that is true iff `T` has a public destructor. NB A compile error will occur if T has non-public destructor. */ template< typename T> struct has_destructor { /* Has destructor :) */ template  static std::true_type test(decltype(std::declval().~A()) *) { return std::true_type(); } /* Has no destructor :( */ template static std::false_type test(...) { return std::false_type(); } /* This will be either `std::true_type` or `std::false_type` */ typedef decltype(test(0)) type; static const bool value = type::value; /* Which is it? */ }; #endif // EOF 

con solo la restricción de que T debe tener un destructor público para ser invocado legalmente en la expresión del argumento de decltype(std::declval().~A()) . ( has_destructor es una adaptación simplificada de la plantilla de introspección de métodos que contribuí aquí ).

El significado de esa expresión de argumento std::declval().~A() puede ser oscura para algunos, específicamente std::declval() . La plantilla de función std::declval() se define en y devuelve un T&& (valor-referencia a T ), aunque solo se puede invocar en contextos no evaluados, como el argumento de decltype . Entonces, el significado de std::declval().~A() es una llamada a ~A() sobre algún A dado . std::declval() nos sirve bien al obviar la necesidad de que exista cualquier constructor público de T , o que sepamos al respecto.

Por consiguiente, el tipo de argumento de la sonda SFINAE para la “sobrecarga Sí” es: apunta al tipo del destructor de A , y la test(0) coincidirá con esa sobrecarga en caso de que exista un tipo como el destructor de A , para A = T

Con has_destructor en la mano, y su limitación a los valores públicamente destructibles de T firmeza en mente, puede probar si una clase T se define en algún punto de su código asegurándose de declararlo antes de hacer la pregunta. Aquí hay un progtwig de prueba.

 #include "has_destructor.h" #include  class bar {}; // Defined template< class CharT, class Traits > class basic_iostream; //Defined template struct vector; //Undefined class foo; // Undefined int main() { std::cout < < has_destructor::value < < std::endl; std::cout << has_destructor>::value < < std::endl; std::cout << has_destructor::value < < std::endl; std::cout << has_destructor>::value < < std::endl; std::cout << has_destructor::value < < std::endl; std::count << std::has_trivial_destructor::value < < std::endl; return 0; } 

Construido con GCC 4.6.3, esto le dirá que las 2 // Defined clases // Defined tienen destructores y las 2 // Undefined clases no // Defined no. La quinta línea de salida dirá que int es destructible, y la línea final mostrará que std::has_trivial_destructor está de acuerdo. Si queremos restringir el campo a los tipos de clases, se puede aplicar std::is_class después de determinar que T es destructible.

Visual C ++ 2010 no proporciona std::declval() . Para admitir ese comstackdor, puede agregar lo siguiente en la parte superior de has_destructor.h :

 #ifdef _MSC_VER namespace std { template  typename add_rvalue_reference::type declval(); } #endif 

Con SFINAE, no. Creo que los trucos de búsqueda de nombres son la manera de hacer esto. Si no tiene miedo de insertar un nombre en el espacio de nombres de la biblioteca:

 namespace lib { #if DEFINE_A class A; #endif } namespace { struct local_tag; using A = local_tag; } namespace lib { template  A is_a_defined(); } constexpr bool A_is_defined = !std::is_same::value; 

Manifestación.

Si se declara A en el espacio de nombre global:

 #if DEFINE_A class A; #endif namespace { struct local_tag; using A = local_tag; } namespace foo { template  ::A is_a_defined(); } constexpr bool A_is_defined = !std::is_same::value; 

Manifestación.

Todavía no encontré una respuesta satisfactoria en esta publicación …

Mike Kinghan comenzó la respuesta correcta y dijo algo inteligente:

Entonces, el problema es determinar si T es un tipo de clase definido.

Pero

sizeof (T) no es de ayuda aquí

no es correcto…

Aquí es cómo puedes hacerlo con sizeof(T) :

 template  struct is_defined { static constexpr bool value = false; }; template  struct is_defined 0)>> { static constexpr bool value = true; }; 

De acuerdo, creo que encontré la manera de hacerlo, aunque puede haber formas mejores. Supongamos que tenemos la clase A que se incluye en algunas instancias de la biblioteca y no en otras. El truco es definir un constructor especial de conversión privada en A y luego usar SFINAE para detectar el constructor de conversión. Cuando A está incluido, la detección tiene éxito; cuando no lo es, la detección falla.

Aquí hay un ejemplo concreto. Primero, el encabezado de la plantilla de detección, class_defined.hpp:

 struct class_defined_helper { }; template< typename T > struct class_defined { typedef char yes; typedef long no; static yes test( T const & ); static no test( ... ); enum { value = sizeof( test( class_defined_helper( )) == sizeof( yes ) }; }; #define CLASS_DEFINED_CHECK( type ) \ type( class_defined_helper const & ); \ \ friend struct class_defined< type >; 

Ahora un encabezado que contiene una definición de clase, blah.hpp:

 #include "class_defined.hpp" #ifdef INCLUDE_BLAH class blah { CLASS_DEFINED_CHECK( blah ); }; #else class blah; #endif 

Ahora el archivo fuente, main.cpp:

 #include "blah.hpp" int main( ) { std::cout < < class_defined< blah >::value < < std::endl; } 

Comstackdo con BLAH_INCLUDED define esto imprime 1. Sin BLAH_INCLUDED definido imprime 0. Desafortunadamente esto todavía requiere una statement directa de la clase para comstackr en ambos casos. No veo una manera de evitar eso.