c++ c++11 final template-meta-programming typetraits

Comprobando que existe un miembro, posiblemente en una clase base, versión C++ 11



c++11 final (2)

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

Ahora, es simplemente más simple 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; }

Puede usar 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 lo usas así:

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 este cambio de cosas en realidad no es tan fácil. En general, es mucho más fácil cuando solo existe SFINAE, y solo desea enable_if un método y no proporcionar ninguna alternativa:

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

Si la sustitución 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 múltiples expresiones en decltype y solo el último realmente decide el tipo. Práctico para verificar múltiples operaciones.

En https://stackoverflow.com/a/1967183/134841 , se proporciona una solución para comprobar estáticamente si un miembro existe, posiblemente en una subclase de un tipo:

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 las clases final C ++ 11, porque hereda de la clase bajo prueba, lo que previene el final .

OTOH, este:

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 de reserve(int/size_t) en las clases base.

¿Hay una implementación de esta metafunción que ambos encuentren reserved() en clases base de T y aún funcione si T es final ?


Una versión que también se basa en decltype pero no en pasar tipos arbitrarios a (...) [que de hecho no es un problema, ver el comentario de 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>& no admite reserve : aquí se inspeccionan las expresiones, no los tipos. La pregunta que responde este rasgo es "Dado un lval para tal tipo T , son las expresiones lval.reserve(0); well lval.reserve(0); ". No es idéntico a la pregunta "¿Este tipo o cualquiera de sus tipos básicos tiene un miembro de reserve declarado".

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

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