Verificar si una clase tiene una función miembro de una determinada firma

Estoy pidiendo un truco de plantilla para detectar si una clase tiene una función miembro específica de una firma determinada.

El problema es similar al que se cita aquí http://www.gotw.ca/gotw/071.htm pero no es lo mismo: en el ítem del libro de Sutter él respondió a la pregunta que una clase C DEBE PROVEER una función de miembro con una firma particular, de lo contrario el progtwig no comstackrá. En mi problema, necesito hacer algo si una clase tiene esa función, de lo contrario, hago “algo más”.

Un problema similar fue enfrentado por boost :: serialization pero no me gusta la solución que adoptaron: una función de plantilla que invoca por defecto una función gratuita (que debe definir) con una firma particular a menos que defina una función de miembro particular ( en su caso “serializar” que toma 2 parámetros de un tipo dado) con una firma particular, de lo contrario, ocurrirá un error de comstackción. Eso es implementar una serialización intrusa y no intrusiva.

No me gusta esa solución por dos razones:

  1. Para no ser intrusivo, debe anular la función “serializar” global que está en el espacio de nombres boost :: serialization, por lo que tiene IN YOUR CLIENT CODE (CÓDIGO DEL CLIENTE) para abrir el espacio de nombres y la serialización del espacio de nombres.
  2. La stack para resolver ese lío era de 10 a 12 funciones invocaciones.

Necesito definir un comportamiento personalizado para las clases que no tienen esa función de miembro, y mis entidades están dentro de espacios de nombres diferentes (y no quiero anular una función global definida en un espacio de nombres mientras estoy en otro)

¿Me puede dar una pista para resolver este rompecabezas?

No estoy seguro si lo entiendo correctamente, pero puede explotar SFINAE para detectar la presencia de funciones en tiempo de comstackción. Ejemplo de mi código (prueba si la clase tiene la función miembro size_t used_memory () const).

template struct HasUsedMemoryMethod { template struct SFINAE {}; template static char Test(SFINAE*); template static int Test(...); static const bool Has = sizeof(Test(0)) == sizeof(char); }; template void ReportMemUsage(const TMap& m, std::true_type) { // We may call used_memory() on m here. } template void ReportMemUsage(const TMap&, std::false_type) { } template void ReportMemUsage(const TMap& m) { ReportMemUsage(m, std::integral_constant::Has>()); } 

Aquí hay una implementación posible que se basa en las características de C ++ 11. Detecta correctamente la función, incluso si es heredada (a diferencia de la solución en la respuesta aceptada, como Mike Kinghan observa en su respuesta ).

La función que este fragmento de prueba prueba se llama serialize :

 #include  // Primary template with a static assertion // for a meaningful error message // if it ever gets instantiated. // We could leave it undefined if we didn't care. template struct has_serialize { static_assert( std::integral_constant::value, "Second template parameter needs to be of function type."); }; // specialization that does the checking template struct has_serialize { private: template static constexpr auto check(T*) -> typename std::is_same< decltype( std::declval().serialize( std::declval()... ) ), Ret // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >::type; // attempt to call it and see if the return type is correct template static constexpr std::false_type check(...); typedef decltype(check(0)) type; public: static constexpr bool value = type::value; }; 

Uso:

 struct X { int serialize(const std::string&) { return 42; } }; struct Y : X {}; std::cout < < has_serialize::value; // will print 1 

La respuesta aceptada a esta pregunta de la introspección de la función miembro de comstackción, aunque es justamente popular, tiene un problema que se puede observar en el siguiente progtwig:

 #include  #include  #include  /* Here we apply the accepted answer's technique to probe for the the existence of `ET::operator*() const` */ template struct has_const_reference_op { template struct SFINAE {}; template static char Test(SFINAE*); template static int Test(...); static const bool value = sizeof(Test(0)) == sizeof(char); }; using namespace std; /* Here we test the `std::` smart pointer templates, including the deprecated `auto_ptr`, to determine in each case whether T = (the template instantiated for `int`) provides `int & T::operator*() const` - which all of them in fact do. */ int main(void) { cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op,int &>::value < < endl; return 0; } 

Construido con GCC 4.6.3, el progtwig emite 110 - informándonos que T = std::shared_ptr no proporciona int & T::operator*() const .

Si todavía no está familiarizado con este problema, entonces una mirada a la definición de std::shared_ptr en el encabezado arrojará luz. En esa implementación, std::shared_ptr se deriva de una clase base de la cual hereda el operator*() const . Por lo tanto, la instanciación de plantilla SFINAE que constituye "encontrar" el operador para U = std::shared_ptr no ocurrirá, porque std::shared_ptr no tiene operator*() en su propio derecho y la instanciación de la plantilla no "hacen herencia".

Este inconveniente no afecta el conocido enfoque SFINAE, usando "The sizeof () Trick", para detectar simplemente si T tiene alguna función miembro mf (ver por ejemplo, esta respuesta y comentarios). Pero establecer que T::mf existe a menudo (¿por lo general?) No es lo suficientemente bueno: es posible que también deba establecer que tiene una firma deseada. Ahí es donde puntúa la técnica ilustrada. La variante punteada de la firma deseada se inscribe en un parámetro de un tipo de plantilla que debe cumplir &T::mf para que la sonda SFINAE tenga éxito. Pero esta técnica de instancia de plantilla da la respuesta incorrecta cuando T::mf se hereda.

Una técnica SFINAE segura para la introspección en tiempo de comstackción de T::mf debe evitar el uso de &T::mf dentro de un argumento de plantilla para instanciar un tipo del que depende la resolución de la plantilla de función SFINAE. En cambio, la resolución de la función de plantilla SFINAE puede depender únicamente de las declaraciones de tipo pertinentes y pertinentes utilizadas como tipos de argumento de la función de sonda SFINAE sobrecargada.

A modo de respuesta a la pregunta que cumple con esta restricción, ilustraré la detección en tiempo de comstackción de ET::operator*() const , para arbitrarios T y E El mismo patrón se aplicará mutatis mutandis para buscar cualquier otra firma de método miembro.

 #include  /*! The template `has_const_reference_op` exports a boolean constant `value that is true iff `T` provides `ET::operator*() const` */ template< typename T, typename E> struct has_const_reference_op { /* SFINAE operator-has-correct-sig :) */ template static std::true_type test(E (A::*)() const) { return std::true_type(); } /* SFINAE operator-exists :) */ template  static decltype(test(&A::operator*)) test(decltype(&A::operator*),void *) { /* Operator exists. What about sig? */ typedef decltype(test(&A::operator*)) return_type; return return_type(); } /* SFINAE game over :( */ template static std::false_type test(...) { return std::false_type(); } /* This will be either `std::true_type` or `std::false_type` */ typedef decltype(test(0,0)) type; static const bool value = type::value; /* Which is it? */ }; 

En esta solución, la prueba de función de sonda SFINAE sobrecargada test() se "invoca recursivamente". (Por supuesto, no se invoca en absoluto, simplemente tiene los tipos de devolución de invocaciones hipotéticas resueltas por el comstackdor).

Necesitamos sondear al menos uno y como máximo dos puntos de información:

  • ¿Existe T::operator*() en absoluto? Si no, hemos terminado.
  • Dado que T::operator*() existe, ¿es su firma ET::operator*() const ?

Obtenemos las respuestas evaluando el tipo de devolución de una sola llamada a test(0,0) . Eso es hecho por:

  typedef decltype(test(0,0)) type; 

Esta llamada puede resolverse para /* SFINAE operator-exists :) */ overload of test() , o podría resolverse en el /* SFINAE game over :( */ overload. No puede resolverse en /* SFINAE operator-has-correct-sig :) */ overload, porque ese solo espera un argumento y estamos pasando dos.

¿Por qué estamos pasando dos? Simplemente para forzar la resolución para excluir /* SFINAE operator-has-correct-sig :) */ . El segundo argumento no tiene otro significado.

Esta llamada a test(0,0) se resolverá a /* SFINAE operator-exists :) */ en el caso de que el primer argumento 0 satisfaga el primer tipo de parámetro de esa sobrecarga, que es decltype(&A::operator*) , con A = T 0 satisfará ese tipo en caso de que T::operator* exista.

Supongamos que el comstackdor dice que sí a eso. Luego va con /* SFINAE operator-exists :) */ y necesita determinar el tipo de retorno de la llamada de función, que en ese caso es decltype(test(&A::operator*)) - el tipo de devolución de otro llamada a test() .

Esta vez, solo aprobamos un argumento, &A::operator* , que ahora sabemos que existe, o no estaríamos aquí. Una llamada a test(&A::operator*) podría resolverse en /* SFINAE operator-has-correct-sig :) */ o de nuevo para resolver en /* SFINAE game over :( */ . La llamada coincidirá /* SFINAE operator-has-correct-sig :) */ just in case &A::operator* satisface el tipo de parámetro único de esa sobrecarga, que es E (A::*)() const , con A = T

El comstackdor dirá Sí aquí si T::operator* tiene esa firma deseada, y nuevamente tiene que evaluar el tipo de devolución de la sobrecarga. No más "recurrencias" ahora: es std::true_type .

Si el comstackdor no elige /* SFINAE operator-exists :) */ para la test(0,0) llamada test(0,0) o no elige /* SFINAE operator-has-correct-sig :) */ para la test(&A::operator*) llamada test(&A::operator*) , luego, en cualquier caso, va con /* SFINAE game over :( */ y el tipo de devolución final es std::false_type .

Aquí hay un progtwig de prueba que muestra la plantilla que produce las respuestas esperadas en una muestra variada de casos (GCC 4.6.3 nuevamente).

 // To test struct empty{}; // To test struct int_ref { int & operator*() const { return *_pint; } int & foo() const { return *_pint; } int * _pint; }; // To test struct sub_int_ref : int_ref{}; // To test template struct ee_ref { E & operator*() { return *_pe; } E & foo() const { return *_pe; } E * _pe; }; // To test struct sub_ee_ref : ee_ref{}; using namespace std; #include  #include  #include  int main(void) { cout < < "Expect Yes" << endl; cout << has_const_reference_op,int &>::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op::iterator,int &>::value; cout < < has_const_reference_op::const_iterator, int const &>::value; cout < < has_const_reference_op::value; cout < < has_const_reference_op::value < < endl; cout << "Expect No" << endl; cout << has_const_reference_op::value; cout < < has_const_reference_op,char &>::value; cout < < has_const_reference_op,int const &>::value; cout < < has_const_reference_op,int>::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op,int &>::value; cout < < has_const_reference_op::value; cout < < has_const_reference_op::value < < endl; return 0; } 

¿Hay nuevos defectos en esta idea? ¿Puede hacerse más genérico sin caer una vez más en la trampa que evita?

Esto debería ser suficiente, si conoce el nombre de la función miembro que está esperando. (En este caso, la función bla no crea una instancia si no hay una función miembro (escribir una que funcione de todos modos es difícil porque hay una falta de especialización parcial de funciones. Puede que necesite usar plantillas de clase) También, la estructura de habilitación (que es similar a enable_if) también podría ser una plantilla en el tipo de función que desea que tenga como miembro.

 template  struct enable { typedef T type; }; template  typename enable::type bla (T&); struct A { void i(); }; struct B { int i(); }; int main() { A a; B b; bla(b); bla(a); } 

Aquí hay algunos fragmentos de uso: * Las agallas de todo esto están más abajo

Verifique el miembro x en una clase determinada. Podría ser var, func, class, union o enum:

 CREATE_MEMBER_CHECK(x); bool has_x = has_member_x::value; 

Verificar la función miembro void x() :

 //Func signature MUST have T as template variable here... simpler this way :\ CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x); bool has_func_sig_void__x = has_member_func_void__x::value; 

Verifique la variable de miembro x :

 CREATE_MEMBER_VAR_CHECK(x); bool has_var_x = has_member_var_x::value; 

Verifique la clase de miembro x :

 CREATE_MEMBER_CLASS_CHECK(x); bool has_class_x = has_member_class_x::value; 

Verifique la unión de miembros x :

 CREATE_MEMBER_UNION_CHECK(x); bool has_union_x = has_member_union_x::value; 

Verifique la enumeración del miembro x :

 CREATE_MEMBER_ENUM_CHECK(x); bool has_enum_x = has_member_enum_x::value; 

Verifique cualquier función miembro x independientemente de la firma:

 CREATE_MEMBER_CHECK(x); CREATE_MEMBER_VAR_CHECK(x); CREATE_MEMBER_CLASS_CHECK(x); CREATE_MEMBER_UNION_CHECK(x); CREATE_MEMBER_ENUM_CHECK(x); CREATE_MEMBER_FUNC_CHECK(x); bool has_any_func_x = has_member_func_x::value; 

O

 CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above. bool has_any_func_x = has_member_func_x::value; 

Detalles y núcleo:

 /* - Multiple inheritance forces ambiguity of member names. - SFINAE is used to make aliases to member names. - Expression SFINAE is used in just one generic has_member that can accept any alias we pass it. */ //Variadic to force ambiguity of class members. C++11 and up. template  struct ambiguate : public Args... {}; //Non-variadic version of the line above. //template  struct ambiguate : public A, public B {}; template struct got_type : std::false_type {}; template struct got_type : std::true_type { typedef A type; }; template struct sig_check : std::true_type {}; template struct has_member { template static char ((&f(decltype(&C::value))))[1]; template static char ((&f(...)))[2]; //Make sure the member name is consistently spelled the same. static_assert( (sizeof(f(0)) == 1) , "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified." ); static bool const value = sizeof(f(0)) == 2; }; 

Macros (El Diablo!):

CREATE_MEMBER_CHECK:

 //Check for any member with given name, whether var, func, class, union, enum. #define CREATE_MEMBER_CHECK(member) \ \ template \ struct Alias_##member; \ \ template \ struct Alias_##member < \ T, std::integral_constant::value> \ > { static const decltype(&T::member) value; }; \ \ struct AmbiguitySeed_##member { char member; }; \ \ template \ struct has_member_##member { \ static const bool value \ = has_member< \ Alias_##member> \ , Alias_##member \ >::value \ ; \ } 

CREATE_MEMBER_VAR_CHECK:

 //Check for member variable with given name. #define CREATE_MEMBER_VAR_CHECK(var_name) \ \ template \ struct has_member_var_##var_name : std::false_type {}; \ \ template \ struct has_member_var_##var_name< \ T \ , std::integral_constant< \ bool \ , !std::is_member_function_pointer::value \ > \ > : std::true_type {} 

CREATE_MEMBER_FUNC_SIG_CHECK:

 //Check for member function with given name AND signature. #define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \ \ template \ struct has_member_func_##templ_postfix : std::false_type {}; \ \ template \ struct has_member_func_##templ_postfix< \ T, std::integral_constant< \ bool \ , sig_check::value \ > \ > : std::true_type {} 

CREATE_MEMBER_CLASS_CHECK:

 //Check for member class with given name. #define CREATE_MEMBER_CLASS_CHECK(class_name) \ \ template \ struct has_member_class_##class_name : std::false_type {}; \ \ template \ struct has_member_class_##class_name< \ T \ , std::integral_constant< \ bool \ , std::is_class< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_UNION_CHECK:

 //Check for member union with given name. #define CREATE_MEMBER_UNION_CHECK(union_name) \ \ template \ struct has_member_union_##union_name : std::false_type {}; \ \ template \ struct has_member_union_##union_name< \ T \ , std::integral_constant< \ bool \ , std::is_union< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_ENUM_CHECK:

 //Check for member enum with given name. #define CREATE_MEMBER_ENUM_CHECK(enum_name) \ \ template \ struct has_member_enum_##enum_name : std::false_type {}; \ \ template \ struct has_member_enum_##enum_name< \ T \ , std::integral_constant< \ bool \ , std::is_enum< \ typename got_type::type \ >::value \ > \ > : std::true_type {} 

CREATE_MEMBER_FUNC_CHECK:

 //Check for function with given name, any signature. #define CREATE_MEMBER_FUNC_CHECK(func) \ template \ struct has_member_func_##func { \ static const bool value \ = has_member_##func::value \ && !has_member_var_##func::value \ && !has_member_class_##func::value \ && !has_member_union_##func::value \ && !has_member_enum_##func::value \ ; \ } 

CREATE_MEMBER_CHECKS:

 //Create all the checks for one member. Does NOT include func sig checks. #define CREATE_MEMBER_CHECKS(member) \ CREATE_MEMBER_CHECK(member); \ CREATE_MEMBER_VAR_CHECK(member); \ CREATE_MEMBER_CLASS_CHECK(member); \ CREATE_MEMBER_UNION_CHECK(member); \ CREATE_MEMBER_ENUM_CHECK(member); \ CREATE_MEMBER_FUNC_CHECK(member) 

Puede usar std :: is_member_function_pointer

 class A { public: void foo() {}; } bool test = std::is_member_function_pointer::value; 

Llegué con el mismo tipo de problema, y ​​encontré las soluciones propuestas aquí muy interesantes … pero tenía el requisito de una solución que:

  1. Detecta funciones heredadas también;
  2. Es compatible con comstackdores no C ++ 11 listos (por lo que no decltype)

Encontré otro hilo proponiendo algo como esto, basado en una discusión BOOST . Aquí está la generalización de la solución propuesta como statement de dos macros para la clase de rasgos, siguiendo el modelo de las clases boost :: has_ ​​* .

 #include  #include  /// Has constant function /** \param func_ret_type Function return type \param func_name Function name \param ... Variadic arguments are for the function parameters */ #define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \ __DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__) /// Has non-const function /** \param func_ret_type Function return type \param func_name Function name \param ... Variadic arguments are for the function parameters */ #define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \ __DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__) // Traits content #define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...) \ template \ < typename Type, \ bool is_class = boost::is_class::value \ > \ class has_func_ ## func_name; \ template \ class has_func_ ## func_name \ {public: \ BOOST_STATIC_CONSTANT( bool, value = false ); \ typedef boost::false_type type; \ }; \ template \ class has_func_ ## func_name \ { struct yes { char _foo; }; \ struct no { yes _foo[2]; }; \ struct Fallback \ { func_ret_type func_name( __VA_ARGS__ ) \ UTILITY_OPTIONAL(func_const,const) {} \ }; \ struct Derived : public Type, public Fallback {}; \ template  class Helper{}; \ template  \ static no deduce(U*, Helper \ < func_ret_type (Fallback::*)( __VA_ARGS__ ) \ UTILITY_OPTIONAL(func_const,const), \ &U::func_name \ >* = 0 \ ); \ static yes deduce(...); \ public: \ BOOST_STATIC_CONSTANT( \ bool, \ value = sizeof(yes) \ == sizeof( deduce( static_cast(0) ) ) \ ); \ typedef ::boost::integral_constant type; \ BOOST_STATIC_CONSTANT(bool, is_const = func_const); \ typedef func_ret_type return_type; \ typedef ::boost::mpl::vector< __VA_ARGS__ > args_type; \ } // Utility functions #define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ ) #define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ ) #define __UTILITY_OPTIONAL_0(...) #define __UTILITY_OPTIONAL_1(...) __VA_ARGS__ 

Estas macros se expanden a una clase de rasgos con el siguiente prototipo:

 template class has_func_[func_name] { public: /// Function definition result value /** Tells if the tested function is defined for type T or not. */ static const bool value = true | false; /// Function definition result type /** Type representing the value attribute usable in http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html */ typedef boost::integral_constant type; /// Tested function constness indicator /** Indicates if the tested function is const or not. This value is not deduced, it is forced depending on the user call to one of the traits generators. */ static const bool is_const = true | false; /// Tested function return type /** Indicates the return type of the tested function. This value is not deduced, it is forced depending on the user's arguments to the traits generators. */ typedef func_ret_type return_type; /// Tested function arguments types /** Indicates the arguments types of the tested function. This value is not deduced, it is forced depending on the user's arguments to the traits generators. */ typedef ::boost::mpl::vector< __VA_ARGS__ > args_type; }; 

Entonces, ¿cuál es el uso típico que uno puede hacer de esto?

 // We enclose the traits class into // a namespace to avoid collisions namespace ns_0 { // Next line will declare the traits class // to detect the member function void foo(int,int) const DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int); } // we can use BOOST to help in using the traits #include  // Here is a function that is active for types // declaring the good member function template inline typename boost::enable_if< ns_0::has_func_foo >::type foo_bar(const T &_this_, int a=0, int b=1) { _this_.foo(a,b); } // Here is a function that is active for types // NOT declaring the good member function template inline typename boost::disable_if< ns_0::has_func_foo >::type foo_bar(const T &_this_, int a=0, int b=1) { default_foo(_this_,a,b); } // Let us declare test types struct empty { }; struct direct_foo { void foo(int,int); }; struct direct_const_foo { void foo(int,int) const; }; struct inherited_const_foo : public direct_const_foo { }; // Now anywhere in your code you can seamlessly use // the foo_bar function on any object: void test() { int a; foo_bar(a); // calls default_foo empty b; foo_bar(b); // calls default_foo direct_foo c; foo_bar(c); // calls default_foo (member function is not const) direct_const_foo d; foo_bar(d); // calls d.foo (member function is const) inherited_const_foo e; foo_bar(e); // calls e.foo (inherited member function) } 

Aquí hay una versión más simple de la respuesta de Mike Kinghan. Esto detectará métodos heredados. También verificará la firma exacta (a diferencia del enfoque de jrok que permite conversiones de argumentos).

 template  class HasGreetMethod { template  static std::true_type testSignature(void (T::*)(const char*) const); template  static decltype(testSignature(&T::greet)) test(std::nullptr_t); template  static std::false_type test(...); public: using type = decltype(test(nullptr)); static const bool value = type::value; }; struct A { void greet(const char* name) const; }; struct Derived : A { }; static_assert(HasGreetMethod::value, ""); 

Ejemplo ejecutable

Para lograr esto necesitaremos usar:

  1. Plantilla de función sobrecarga con diferentes tipos de devolución según si el método está disponible
  2. De acuerdo con los type_traits en el encabezado type_traits , querremos devolver un true_type o false_type de nuestras sobrecargas
  3. Declare la sobrecarga true_type esperando una sobrecarga int y la false_type esperando que exploten los parámetros variables: “La prioridad más baja de la conversión de puntos suspensivos en la resolución de sobrecarga”
  4. Al definir la especificación de plantilla para la función true_type utilizaremos declval y decltype lo que nos permite detectar la función independientemente de las diferencias o sobrecargas del tipo de devolución entre los métodos

Aquí puede ver un ejemplo en vivo de esto, pero lo explicaré a continuación:

Quiero verificar la existencia de una función llamada test que toma un tipo convertible de int , entonces necesitaría declarar estas dos funciones:

 template ().test(declval))> static true_type hasTest(int); template  static false_type hasTest(...); 
  • decltype(hasTest(0))::value is true (Tenga en cuenta que no es necesario crear una funcionalidad especial para hacer frente a la sobrecarga de void a::test(int) se acepta void a::test(int) )
  • decltype(hasTest(0))::value es true (porque int es convertible a double int b::test(double) es aceptado, independientemente del tipo de devolución)
  • decltype(hasTest(0))::value es false ( c no tiene un método llamado test que acepte un tipo convertible de int por lo que no se acepta)

Esta solución tiene 2 inconvenientes:

  1. Requiere una statement por método de un par de funciones
  2. Crea contaminación del espacio de nombres, especialmente si queremos probar nombres similares, por ejemplo, ¿qué nombre daríamos a una función que quería probar un método de test() ?

Por lo tanto, es importante que estas funciones se declaren en un espacio de nombres de detalles o, idealmente, si solo se van a usar con una clase, deben declararse de forma privada en esa clase. Con ese fin, he escrito una macro para ayudarlo a abstraer esta información:

 #define FOO(FUNCTION, DEFINE) template ().FUNCTION)> static true_type __ ## DEFINE(int); \ template  static false_type __ ## DEFINE(...); \ template  using DEFINE = decltype(__ ## DEFINE(0)); 

Puedes usar esto como:

 namespace details { FOO(test(declval()), test_int) FOO(test(), test_void) } 

Posteriormente, los details::test_int::value llamada details::test_int::value o details::test_void::value arrojarían true o false a los efectos del código en línea o la metaprogtwigción.

Para no ser intrusivo, también puede poner en serialize en el espacio de nombres de la clase que se está serializando, o de la clase de archivo, gracias a la búsqueda de Koenig . Consulte Espacios de nombres para anulaciones de funciones gratuitas para obtener más detalles. 🙂

La apertura de cualquier espacio de nombres para implementar una función gratuita es Simplemente Incorrecta. (por ejemplo, se supone que no debes abrir el espacio de nombres std para implementar el swap para tus propios tipos, sino que debes usar la búsqueda de Koenig en su lugar).

Bueno. Segundo bash. Está bien si no te gusta este tampoco, estoy buscando más ideas.

El artículo de Herb Sutter habla sobre los rasgos. Por lo tanto, puede tener una clase de rasgos cuya instanciación predeterminada tenga el comportamiento alternativo, y para cada clase en la que exista su función de miembro, la clase de rasgos estará especializada para invocar la función de miembro. Creo que el artículo de Herb menciona una técnica para hacer esto, de modo que no implique muchas copias y pegados.

Como dije, sin embargo, quizás no quiera el trabajo adicional relacionado con las clases de “etiquetado” que implementan a ese miembro. En ese caso, estoy buscando una tercera solución …

Creo que la respuesta que está buscando está aquí.

http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Compile-Time

y un ejemplo un poco más completo aquí

http://pastie.org/298994

Utilizo la técnica para detectar la presencia de un operador ostream de soporte < < en la clase en cuestión y luego genero un bit diferente de código dependiendo.

No creí que fuera posible antes de encontrar la solución vinculada, pero es un truco muy bueno. Dedique el tiempo a entender el código y vale la pena.

Puntilla

Sin el soporte C ++ 11 ( decltype ) esto podría funcionar:

SSCCE

 #include  using namespace std; struct A { void foo(void); }; struct Aa: public A { }; struct B { }; struct retA { int foo(void); }; struct argA { void foo(double); }; struct constA { void foo(void) const; }; struct varA { int foo; }; template struct FooFinder { typedef char true_type[1]; typedef char false_type[2]; template struct TypeSink; template static true_type &match(U); template static true_type &test(TypeSink( &U::foo ) )> *); template static false_type &test(...); enum { value = (sizeof(test(0, 0)) == sizeof(true_type)) }; }; int main() { cout < < FooFinder::value < < endl; cout << FooFinder::value < < endl; cout << FooFinder::value < < endl; cout << FooFinder::value < < endl; cout << FooFinder::value < < endl; cout << FooFinder::value < < endl; cout << FooFinder::value < < endl; } 

¿Cómo funciona?

A , Aa y B son las clases en cuestión, siendo Aa la especial que hereda el miembro que estamos buscando.

En el FooFinder true_type y false_type son los reemplazos de las correspondientes clases de C ++ 11. También para la comprensión de la metaprogtwigción de plantillas, revelan la base misma del SFINAE-sizeof-truco.

TypeSink es una estructura de plantilla que se utiliza más adelante para hundir el resultado integral del operador sizeof en una instanciación de plantilla para formar un tipo.

La función de match es otra clase de plantilla SFINAE que se deja sin una contraparte genérica. Por lo tanto, solo se puede instanciar si el tipo de su argumento coincide con el tipo para el que estaba especializado.

Both the test functions together with the enum declaration finally form the central SFINAE pattern. There is a generic one using an ellipsis that returns the false_type and a counterpart with more specific arguments to take precedence.

To be able to instantiate the test function with a template argument of T , the match function must be instantiated, as its return type is required to instantiate the TypeSink argument. The caveat is that &U::foo , being wrapped in a function argument, is not referred to from within a template argument specialization, so inherited member lookup still takes place.