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


En https://stackoverflow.com/a/1967183/134841 , se proporciona una solución para verificar estáticamente si existe un miembro, 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 C++11 final, porque hereda de la clase bajo prueba, lo que final impide.

OTOH, este:{[12]]}

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 encuentra el método 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 todavía funciona si T es final?

Author: Community, 2012-03-02

2 answers

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

Ahora, es 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; }

Luego puedes 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 existen SFINAE {y solo quieres enable_if uno 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 coma) puede encadenar múltiples expresiones en decltype y solo la última decide el tipo. Práctico para comprobar múltiples operaciones.

 48
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-14 06:03:52

Una versión que también se basa en decltype pero no en pasar tipos arbitrarios a (...) [que de hecho no es un problema de todos modos, 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 que, de acuerdo con este rasgo, un tipo como std::vector<int>& soporta reserve: aquí se inspeccionan las expresiones, no los tipos. La pregunta que este rasgo responde es "Dado un lvalue lval para tal tipo T, son las expresiones lval.reserve(0); bien formadas". No es idéntico a la pregunta " ¿Este tipo o cualquiera de sus tipos base tiene un miembro reserve declarado".

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

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

 10
Author: Luc Danton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-03 20:07:31