2012-05-23 19 views
20

¿Es posible detectar si existe una clase en C++ utilizando SFINAE? Si es posible, ¿cómo?¿Cómo detectar la existencia de una clase usando SFINAE?

Supongamos que tenemos una clase que se proporciona únicamente 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.

+1

Quiere decir si una clase contiene una clase particular ¿Educación física? O si existe un tipo en un ámbito de espacio de nombres? (No veo cómo esto último sería útil) – sbabbi

+0

El compilador hará eso por usted. Por favor, explica lo que quieres hacer? –

+0

Para aclarar: ¿la clase es solo un identificador de código fijo, por lo que no depende de la sustitución de ningún parámetro de plantilla? – ndkrempel

Respuesta

25

Si le pedimos al compilador que nos diga algo acerca de un tipo de clase T que no ha sido declarado , tendremos que obtener un error de compilación. No hay manera de que redondee eso. Por lo tanto, si queremos saber si la clase T "existe", donde T podría ni siquiera han sido declarados sin embargo, debemos declarar T primero.

Pero eso está bien, porque la simple declaración T no hará que "existe", ya que lo que debemos entender por T existe es T se define. Y si, después de haber declarado T, , puede determinar si ya está definido, no necesita estar en confusión.

El problema es determinar si T es un tipo de clase definido.

sizeof(T) hay ayuda aquí. Si T no está definido, dará un error incomplete type T. Del mismo modo typeid(T). Tampoco es ninguna buena la elaboración de una sonda SFINAE del tipo T *, porque T *es es un tipo definido , siempre y cuando T ha sido declarado, incluso si no es T. Y ya que estamos obligados a tener una declaración de clase T, std::is_class<T> no es el respuesta tampoco, debido a que la declaración será suficiente para que diga "Sí".

C++ 11 proporciona std::is_constructible<T ...Args> en <type_traits>.¿Puede ofrecer una solución off-the-peg? - dado que si se define T, entonces debe tener al menos un constructor.

No estoy seguro. Si conoce la firma de al menos un constructor público de T, entonces el <type_traits> de GCC (a partir de 4.6.3) efectivamente hará el negocio. Digamos que un constructor público conocido es T::T(int). Entonces:

std::is_constructible<T,int>::value 

será cierto si se ha definido T y falso si T se limitó a declarar.

Pero esto no es portátil. <type_traits> en VC++ 2010 no proporciona todavía std::is_constructible e incluso su std::has_trivial_constructor<T> se vomitar si T no está definido: std::is_constructible más probable cuando no llegan va a seguir su ejemplo. Además, en la eventualidad de que solo existan constructores privados de de T para ofrecer std::is_constructible, incluso GCC barf (que levanta la ceja).

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, el juego más simple y más fuerte que podemos hacer es crear una sonda SFINAE para la existencia de T::~T. sonda

Este SFINAE no puede ser elaborado en la forma de rutina para determinar si T tiene una función miembro ordinario mf - haciendo que el "Sí sobrecarga" de la función de la sonda SFINAE tener un argumento que se define en términos de la tipo de &T::mf. Porque no podemos tomar la dirección de un destructor (o constructor).

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

#ifndef HAS_DESTRUCTOR_H 
#define HAS_DESTRUCTOR_H 

#include <type_traits> 

/*! The template `has_destructor<T>` exports a 
    boolean constant `value that is true iff `T` has 
    a public destructor. 

    N.B. A compile error will occur if T has non-public destructor. 
*/ 
template< typename T> 
struct has_destructor 
{ 
    /* Has destructor :) */ 
    template <typename A> 
    static std::true_type test(decltype(std::declval<A>().~A()) *) { 
     return std::true_type(); 
    } 

    /* Has no destructor :(*/ 
    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)) type; 

    static const bool value = type::value; /* Which is it? */ 
}; 

#endif // EOF 

sólo con la restricción de que T debe tener un destructor pública ser legalmente invocado en la expresión argumento de decltype(std::declval<A>().~A()). (has_destructor<T> es una adaptación simplificada de la plantilla método de introspección contribuí here.)

El significado de esa expresión argumento std::declval<A>().~A() puede ser oscuro para algunos, específicamente std::declval<A>().La plantilla de función std::declval<T>() se define en <type_traits> y devuelve un T&& (rvalue-referencia a T) - aunque sólo podrá ser invocado sin evaluar contextos, como el argumento de decltype. Por lo tanto, el significado de std::declval<A>().~A() es una llamada al ~A() sobre un determinado A. std::declval<A>() nos sirve bien aquí al obviar la necesidad de que haya cualquier constructor público de T, o para que lo sepamos.

En consecuencia, el tipo de argumento de la sonda SFINAE para el "Sí sobrecarga" es: puntero al tipo de destructor de A y test<T>(0) coincidirá que sobrecarga por si acaso hay un tipo tal como destructor de A, por A = T

con has_destructor<T> en la mano - y su limitación a destructibles valores de T firmemente en la mente del público - se puede comprobar si una clase es T definido en algún punto en su código, asegurándose de que declare antes de hacer una pregunta al . Aquí hay un programa de prueba.

#include "has_destructor.h" 
#include <iostream> 

class bar {}; // Defined 
template< 
    class CharT, 
    class Traits 
> class basic_iostream; //Defined 
template<typename T> 
struct vector; //Undefined 
class foo; // Undefined 

int main() 
{ 
    std::cout << has_destructor<bar>::value << std::endl; 
    std::cout << has_destructor<std::basic_iostream<char>>::value 
     << std::endl; 
    std::cout << has_destructor<foo>::value << std::endl; 
    std::cout << has_destructor<vector<int>>::value << std::endl; 
    std::cout << has_destructor<int>::value << std::endl; 
    std::count << std::has_trivial_destructor<int>::value << std::endl; 
    return 0; 
} 

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

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

#ifdef _MSC_VER 
namespace std { 
template <typename T> 
typename add_rvalue_reference<T>::type declval(); 
} 
#endif 
+0

De hecho, es un buen análisis. Creo que es efectivamente equivalente a la solución que propuse, excepto que usa el destructor como un gancho en lugar de requerir un constructor de conversión especial. Aquí hay una solución de compromiso: esta solución funcionará de forma transparente la mayor parte del tiempo, pero la mía funcionará todo el tiempo a costa de invocar una macro en cada definición de clase. –

+0

Hola, excelente respuesta, estoy intentando modificar tu código para trabajar con plantillas de clases, hasta ahora lo compilo cambiando 'typename A/T' a 'template clase A/T' para que coincida con los argumentos de mi plantilla, pero la prueba falla silenciosamente incluso cuando hay un destructor público. ¿Sabes por qué podría ser por favor? Sé que podría crear una instancia de la plantilla para una clase y usar el original, pero estoy usando esta verificación en una macro justo antes de especializar la plantilla, por lo que no puedo crear una instancia antes de mi especialización. ¡Gracias! – stellarpower

+0

Imposible decir sin ver su código. Adaptar la solución a una plantilla de clase funciona de manera directa para mí, por lo que hay algo mal con su código. Le sugiero que haga una nueva pregunta con un título adecuado y diga: "Intento sin éxito adaptar para trabajar en una plantilla de clase. Aquí está mi código ..." –

0

Muy bien, creo que he encontrado una manera de hacer esto, aunque puede haber mejores maneras. 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. En primer lugar la cabecera 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 de origen, main.cpp:

#include "blah.hpp" 

int main() { 
    std::cout << class_defined<blah>::value << std::endl; 
} 

Compilado con BLAH_INCLUDED define esto imprime 1. Sin BLAH_INCLUDED definido imprime 0. Desafortunadamente, esto todavía requiere una declaración directa de la clase para compilar en ambos casos. No veo una manera de evitar eso.

+1

Esto realmente no detecta si el tipo está definido (o incluso declarado), sino más bien si se define una macro. En este caso, podría ser más simple simplemente descartar todo el código y dejarlo como '#if CLASS_DEFINED_MACRO' y luego como el código dependiente. –

+0

No sigo. El punto es que es imposible verificar si se ha declarado un tipo, ya que para verificar que es necesario declararlo. Creo que también es imposible determinar si se define un tipo a menos que tenga un gancho que buscar en el tipo. La macro proporciona ese gancho en forma de un constructor de conversión. Mi solución le permite compilar una biblioteca con y sin una definición de clase y verificar si la clase se define utilizando SFINAE. Eso es lo más cercano que creo que podemos llegar a lo que quiere el OP. Muchos patrones requieren ganchos en tipos. Esa es toda la macro nosotros aquí. –

+0

Excepto que no tiene nada que ver con SFINAE. –

5

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

namespace lib { 
#if DEFINE_A 
class A; 
#endif 
} 

namespace { 
    struct local_tag; 
    using A = local_tag; 
} 

namespace lib { 
    template <typename T = void> 
    A is_a_defined(); 
} 

constexpr bool A_is_defined = 
    !std::is_same<local_tag, decltype(lib::is_a_defined())>::value; 

Demo.

Si A se declara en el espacio de nombres global:

#if DEFINE_A 
class A; 
#endif 

namespace { 
    struct local_tag; 
    using A = local_tag; 
} 

namespace foo { 
    template <typename T = void> 
    ::A is_a_defined(); 
} 

constexpr bool A_is_defined = 
    !std::is_same<local_tag, decltype(foo::is_a_defined())>::value; 

Demo.

2

Todavía lo hizo no encontrar una respuesta satisfactoria en esta publicación ...

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

Así que el problema es determinar si T es un tipo de clase definida.

Pero

sizeof (T) no es ninguna ayuda aquí

no es correcta ...

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

template <class T, class Enable = void> 
struct is_defined 
{ 
    static constexpr bool value = false; 
}; 

template <class T> 
struct is_defined<T, std::enable_if_t<(sizeof(T) > 0)>> 
{ 
    static constexpr bool value = true; 
}; 
+0

Esta respuesta es simple de entender y funciona para mí tanto en GCC 7.2.1 como en VS 2017.Mientras que la respuesta mejor calificada me da errores de "tipo incompleto" en GCC. Felicitaciones por esto :) – Kai

Cuestiones relacionadas