2012-03-02 13 views
27

En https://stackoverflow.com/a/1967183/134841, se proporciona una solución para el control de estáticamente si existe un miembro, posiblemente en una subclase de un tipo:Comprobación existe un miembro, posiblemente en una clase base, C++ versión 11

template <typename Type> 
class has_resize_method 
{ 
    class yes { char m;}; 
    class no { yes m[2];}; 
    struct BaseMixin 
    { 
    void resize(int){} 
    }; 
    struct Base : public Type, public BaseMixin {}; 
    template <typename T, T t> class Helper{}; 
    template <typename U> 
    static no deduce(U*, Helper<void (BaseMixin::*)(), &U::foo>* = 0); 
    static yes deduce(...); 
public: 
    static const bool result = sizeof(yes) == sizeof(deduce((Base*)(0))); 
}; 

Sin embargo , no funciona en C++ 11 final clases, porque hereda de la clase bajo prueba, que final previene.

otoh, éste:

template <typename C> 
struct has_reserve_method { 
private: 
    struct No {}; 
    struct Yes { No no[2]; }; 
    template <typename T, typename I, void(T::*)(I) > struct sfinae {}; 
    template <typename T> static No check(...); 
    template <typename T> static Yes check(sfinae<T,int, &T::reserve> *); 
    template <typename T> static Yes check(sfinae<T,size_t,&T::reserve> *); 
public: 
    static const bool value = sizeof(check<C>(0)) == sizeof(Yes) ; 
}; 

no puede encontrar el método reserve(int/size_t) en baseclasses.

¿Existe una implementación de este metafunción que tanto reserved() encuentra en baseclasses de T y todavía funciona si T es final?

+3

En C++ 11 puede simplemente usar "sfinae para expresiones" en lugar de hacer esta rotonda necesaria en C++ 03. –

+1

@ JohannesSchaub-litb ¡Solicitud de solución! Me encantaría ver la diferencia que hace para este tipo de ejemplo. – stinky472

Respuesta

46

En realidad, las cosas se pusieron mucho más fáciles en C++ 11 gracias a la decltype y maquinaria de enlaces de retorno tardío.

Ahora, es simplemente más fácil de usar métodos para probar esto:

// Culled by SFINAE if reserve does not exist or is not accessible 
template <typename T> 
constexpr auto has_reserve_method(T& t) -> decltype(t.reserve(0), bool()) { 
    return true; 
} 

// Used as fallback when SFINAE culls the template method 
constexpr bool has_reserve_method(...) { return false; } 

Usted puede entonces utilizar esto en una clase, por ejemplo:

template <typename T, bool b> 
struct Reserver { 
    static void apply(T& t, size_t n) { t.reserve(n); } 
}; 

template <typename T> 
struct Reserver <T, false> { 
    static void apply(T& t, size_t n) {} 
}; 

Y que lo utilizan de modo:

template <typename T> 
bool reserve(T& t, size_t n) { 
    Reserver<T, has_reserve_method(t)>::apply(t, n); 
    return has_reserve_method(t); 
} 

O puede elegir un método enable_if:

template <typename T> 
auto reserve(T& t, size_t n) -> typename std::enable_if<has_reserve_method(t), bool>::type { 
    t.reserve(n); 
    return true; 
} 

template <typename T> 
auto reserve(T& t, size_t n) -> typename std::enable_if<not has_reserve_method(t), bool>::type { 
    return false; 
} 

Tenga en cuenta que esta conmutación de cosas en realidad no es tan fácil. En general, es mucho más fácil cuando solo existe SFINAE - y lo que desea es enable_if un método y no proporciona ningún retorno:

template <typename T> 
auto reserve(T& t, size_t n) -> decltype(t.reserve(n), void()) { 
    t.reserve(n); 
} 

Si la sustitución de falla, este método se elimina de la lista de posibles sobrecargas.

Nota: gracias a la semántica de , (el operador de coma) puede encadenar expresiones múltiples en decltype y solo el último realmente decide el tipo. Práctico para verificar múltiples operaciones.

+0

¿'T' no necesita ser un tipo literal para ser un parámetro de una función' constexpr'? Ok, en las plantillas, el 'constexpr' se descarta en el momento de la instanciación cuando no puede ser' constexpr', pero lo está usando en una lista de argumentos de plantilla, donde tiene que ser. –

+0

@Marc: No, casi no tienes restricciones sobre lo que puede ser un parámetro. – Xeo

+0

Ok, esto me dio la dirección correcta. Después de simplificaciones sucesivas, lo hice funcionar requiriendo compatibilidad mínima con C++ 11 (simplemente 'decltype') manteniendo la estructura de la 2da versión de C++ 98, pero reemplazando las versiones' Sí' con una sola plantilla ' decletido estático (static_cast (0) -> isNull(), Yes()) check (int); 'one. –

10

Una versión que también se basa en decltype pero no sobre pasa tipos arbitrarios a (...) [que en realidad es un problema no de todos modos, ver comentario Johannes']:

template<typename> struct Void { typedef void type; }; 

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

template<typename T> 
struct has_reserve< 
    T 
    , typename Void< 
     decltype(std::declval<T&>().reserve(0)) 
    >::type 
>: std::true_type {}; 

me gustaría señalar de acuerdo con este rasgo, un tipo como std::vector<int>& admite reserve: aquí se inspeccionan las expresiones, no los tipos. La pregunta que responde este rasgo es "Dado un lvalue lval para un tipo T, las expresiones lval.reserve(0); están bien formadas". No es idéntico a la pregunta "¿Este tipo o cualquiera de sus tipos básicos tiene un miembro reserve declarado".

Por otro lado, podría decirse que es una característica.Recuerde que el nuevo rasgo C++ 11 es del estilo is_default_constructible, nohas_default_constructor. La distinción es sutil pero tiene méritos. (Encontrar un nombre de mejor ajuste en el estilo de is_*ible que queda como ejercicio.)

En cualquier caso, puede utilizar un rasgo como std::is_class para lograr lo que desea.

+0

¡Este funciona maravillosamente, gracias! – Avio

Cuestiones relacionadas