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.