prototipo programacion plantillas hacer funciones funcion ejercicios dev como ats c++ templates template-meta-programming sfinae

c++ - plantillas - programacion ats funciones



¿Es posible escribir una plantilla para verificar la existencia de una función? (23)

Kit de herramientas de detección

N4502 propone una detección de detección para su inclusión en la biblioteca estándar de C ++ 17 que puede resolver el problema de una manera un tanto elegante. Por otra parte, acaba de ser aceptado en los fundamentos de la biblioteca TS v2. Introduce algunas metafunciones, incluyendo std::is_detected que puede usarse para escribir fácilmente funciones de detección de tipo o función en la parte superior. Aquí es cómo puedes usarlo:

template<typename T> using toString_t = decltype( std::declval<T&>().toString() ); template<typename T> constexpr bool has_toString = std::is_detected_v<toString_t, T>;

Tenga en cuenta que el ejemplo anterior no está probado. El kit de herramientas de detección aún no está disponible en las bibliotecas estándar, pero la propuesta contiene una implementación completa que puede copiar fácilmente si realmente la necesita. Juega bien con la característica de C ++ 17 if constexpr :

template<class T> std::string optionalToString(T* obj) { if constexpr (has_toString<T>) return obj->toString(); else return "toString not defined"; }

Boost.TTI

Otro conjunto de herramientas un tanto idiomático para realizar tal comprobación, aunque menos elegante, es Boost.TTI , introducido en Boost 1.54.0. Para su ejemplo, tendría que usar la macro BOOST_TTI_HAS_MEMBER_FUNCTION . Aquí es cómo puedes usarlo:

#include <boost/tti/has_member_function.hpp> // Generate the metafunction BOOST_TTI_HAS_MEMBER_FUNCTION(toString) // Check whether T has a member function toString // which takes no parameter and returns a std::string constexpr bool foo = has_member_function_toString<T, std::string>::value;

Luego, puede usar el bool para crear un cheque SFINAE.

Explicación

La macro BOOST_TTI_HAS_MEMBER_FUNCTION genera la metafunción has_member_function_toString que toma el tipo comprobado como su primer parámetro de plantilla. El segundo parámetro de plantilla corresponde al tipo de retorno de la función miembro, y los siguientes parámetros corresponden a los tipos de parámetros de la función. El value miembro contiene true si la clase T tiene una función miembro std::string toString() .

Alternativamente, has_member_function_toString puede tomar un puntero de función miembro como parámetro de plantilla. Por lo tanto, es posible reemplazar has_member_function_toString<T, std::string>::value por has_member_function_toString<std::string T::* ()>::value .

¿Es posible escribir una plantilla que cambie el comportamiento dependiendo de si una función miembro determinada está definida en una clase?

Aquí hay un ejemplo simple de lo que me gustaría escribir:

template<class T> std::string optionalToString(T* obj) { if (FUNCTION_EXISTS(T->toString)) return obj->toString(); else return "toString not defined"; }

Entonces, si la class T tiene toString() definida, entonces la usa; De lo contrario, no lo hace. La parte mágica que no sé cómo hacer es la parte "FUNCTION_EXISTS".


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

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

CREATE_MEMBER_CHECK(x); bool has_x = has_member_x<class_to_check_for_x>::value;

Compruebe 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<class_to_check_for_x>::value;

Compruebe la variable miembro x :

CREATE_MEMBER_VAR_CHECK(x); bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

Verifique la clase de miembro x :

CREATE_MEMBER_CLASS_CHECK(x); bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

Compruebe la unión miembro x :

CREATE_MEMBER_UNION_CHECK(x); bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

Compruebe la enumeración de miembros x :

CREATE_MEMBER_ENUM_CHECK(x); bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

Compruebe si hay alguna 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<class_to_check_for_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<class_to_check_for_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 <typename... Args> struct ambiguate : public Args... {}; //Non-variadic version of the line above. //template <typename A, typename B> struct ambiguate : public A, public B {}; template<typename A, typename = void> struct got_type : std::false_type {}; template<typename A> struct got_type<A> : std::true_type { typedef A type; }; template<typename T, T> struct sig_check : std::true_type {}; template<typename Alias, typename AmbiguitySeed> struct has_member { template<typename C> static char ((&f(decltype(&C::value))))[1]; template<typename C> static char ((&f(...)))[2]; //Make sure the member name is consistently spelled the same. static_assert( (sizeof(f<AmbiguitySeed>(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<Alias>(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<typename T, typename = std::true_type> / struct Alias_##member; / / template<typename T> / struct Alias_##member < / T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> / > { static const decltype(&T::member) value; }; / / struct AmbiguitySeed_##member { char member; }; / / template<typename T> / struct has_member_##member { / static const bool value / = has_member< / Alias_##member<ambiguate<T, AmbiguitySeed_##member>> / , Alias_##member<AmbiguitySeed_##member> / >::value / ; / }

CREATE_MEMBER_VAR_CHECK:

//Check for member variable with given name. #define CREATE_MEMBER_VAR_CHECK(var_name) / / template<typename T, typename = std::true_type> / struct has_member_var_##var_name : std::false_type {}; / / template<typename T> / struct has_member_var_##var_name< / T / , std::integral_constant< / bool / , !std::is_member_function_pointer<decltype(&T::var_name)>::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<typename T, typename = std::true_type> / struct has_member_func_##templ_postfix : std::false_type {}; / / template<typename T> / struct has_member_func_##templ_postfix< / T, std::integral_constant< / bool / , sig_check<func_sig, &T::func_name>::value / > / > : std::true_type {}

CREATE_MEMBER_CLASS_CHECK:

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

CREATE_MEMBER_UNION_CHECK:

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

CREATE_MEMBER_ENUM_CHECK:

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

CREATE_MEMBER_FUNC_CHECK:

//Check for function with given name, any signature. #define CREATE_MEMBER_FUNC_CHECK(func) / template<typename T> / struct has_member_func_##func { / static const bool value / = has_member_##func<T>::value / && !has_member_var_##func<T>::value / && !has_member_class_##func<T>::value / && !has_member_union_##func<T>::value / && !has_member_enum_##func<T>::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)


Aunque esta pregunta tiene dos años, me atreveré a agregar mi respuesta. Esperemos que aclare la solución anterior, indiscutiblemente excelente. Tomé las respuestas muy útiles de Nicola Bonelli y Johannes Schaub y las fusioné en una solución que, en mi humilde opinión, es más legible, clara y no requiere la extensión typeof :

template <class Type> class TypeHasToString { // This type won''t compile if the second template parameter isn''t of type T, // so I can put a function pointer type in the first parameter and the function // itself in the second thus checking that the function has a specific signature. template <typename T, T> struct TypeCheck; typedef char Yes; typedef long No; // A helper struct to hold the declaration of the function pointer. // Change it if the function signature changes. template <typename T> struct ToString { typedef void (T::*fptr)(); }; template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*); template <typename T> static No HasToString(...); public: static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes)); };

Lo comprobé con gcc 4.1.2. El crédito va principalmente a Nicola Bonelli y Johannes Schaub, así que dales un voto si mi respuesta te ayuda :)


Bueno, esta pregunta ya tiene una larga lista de respuestas, pero me gustaría enfatizar el comentario de Morwenn: hay una propuesta para C ++ 17 que la hace mucho más sencilla. Consulte N4502 para obtener detalles, pero como un ejemplo independiente, considere lo siguiente.

Esta parte es la parte constante, ponla en un encabezado.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf. template <typename...> using void_t = void; // Primary template handles all types not supporting the operation. template <typename, template <typename> class, typename = void_t<>> struct detect : std::false_type {}; // Specialization recognizes/validates only types supporting the archetype. template <typename T, template <typename> class Op> struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

luego está la parte variable, donde se especifica lo que está buscando (un tipo, un tipo de miembro, una función, una función de miembro, etc.). En el caso del OP:

template <typename T> using toString_t = decltype(std::declval<T>().toString()); template <typename T> using has_toString = detect<T, toString_t>;

El siguiente ejemplo, tomado de N4502 , muestra una sonda más elaborada:

// Archetypal expression for assignment operation. template <typename T> using assign_t = decltype(std::declval<T&>() = std::declval<T const &>()) // Trait corresponding to that archetype. template <typename T> using is_assignable = detect<T, assign_t>;

En comparación con las otras implementaciones descritas anteriormente, esta es bastante simple: un conjunto reducido de herramientas ( void_t y detect ) es suficiente, sin necesidad de macros peludas. Además, se informó (ver N4502 ) que es considerablemente más eficiente (tiempo de compilación y consumo de memoria del compilador) que los enfoques anteriores.

Aquí hay un ejemplo en vivo . Funciona bien con Clang, pero desafortunadamente, las versiones de GCC anteriores a 5.1 siguieron una interpretación diferente del estándar C ++ 11 que hizo que void_t no funcionara como se esperaba. Yakk ya proporcionó la solución: use la siguiente definición de void_t ( void_t en la lista de parámetros funciona pero no como tipo de retorno ):

#if __GNUC__ < 5 && ! defined __clang__ // https://.com/a/28967049/1353549 template <typename...> struct voider { using type = void; }; template <typename...Ts> using void_t = typename voider<Ts...>::type; #else template <typename...> using void_t = void; #endif


C ++ permite que SFINAE se use para esto (observe que con las características de C ++ 11 esto es más sencillo porque admite SFINAE extendido en expresiones casi arbitrarias; lo siguiente se creó para funcionar con compiladores comunes de C ++ 03):

#define HAS_MEM_FUNC(func, name) / template<typename T, typename Sign> / struct name { / typedef char yes[1]; / typedef char no [2]; / template <typename U, U> struct type_check; / template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); / template <typename > static no &chk(...); / static bool const value = sizeof(chk<T>(0)) == sizeof(yes); / }

la plantilla y la macro anteriores intentan crear una instancia de una plantilla, dándole un tipo de puntero de función miembro y el puntero de función miembro real. Si los tipos no se ajustan, SFINAE hace que la plantilla se ignore. Uso como este:

HAS_MEM_FUNC(toString, has_to_string); template<typename T> void doSomething() { if(has_to_string<T, std::string(T::*)()>::value) { ... } else { ... } }

Pero tenga en cuenta que no puede simplemente llamar a esa función toString en la rama if. como el compilador verificará la validez en ambas ramas, eso fallaría en los casos en que la función no exista. Una forma es usar SFINAE una vez más (enable_if también se puede obtener de boost):

template<bool C, typename T = void> struct enable_if { typedef T type; }; template<typename T> struct enable_if<false, T> { }; HAS_MEM_FUNC(toString, has_to_string); template<typename T> typename enable_if<has_to_string<T, std::string(T::*)()>::value, std::string>::type doSomething(T * t) { /* something when T has toString ... */ return t->toString(); } template<typename T> typename enable_if<!has_to_string<T, std::string(T::*)()>::value, std::string>::type doSomething(T * t) { /* something when T doesnt have toString ... */ return "T::toString() does not exist."; }

Diviértete usándolo. La ventaja de esto es que también funciona para funciones miembro sobrecargadas, y también para funciones miembro const (recuerde usar std::string(T::*)() const como el tipo de puntero de función miembro, entonces!).


Esta es una solución de C ++ 11 para el problema general si "Si hiciera X, ¿se compilaría?"

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void` template<class T> using type_sink_t = typename type_sink<T>::type; template<class T, class=void> struct has_to_string : std::false_type {}; / template<class T> struct has_to_string< T, type_sink_t< decltype( std::declval<T>().toString() ) > >: std::true_type {};

El rasgo has_to_string tal que has_to_string<T>::value es true solo si T tiene un método .toString que puede invocarse con 0 argumentos en este contexto.

A continuación, usaría el envío de etiquetas:

namespace details { template<class T> std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) { return obj->toString(); } template<class T> std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) { return "toString not defined"; } } template<class T> std::string optionalToString(T* obj) { return details::optionalToString_helper( obj, has_to_string<T>{} ); }

que tiende a ser más fácil de mantener que las expresiones SFINAE complejas.

Puede escribir estos rasgos con una macro si se encuentra haciéndolo mucho, pero son relativamente simples (unas pocas líneas cada uno), así que quizás no valga la pena:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) / template<class T, class=void> struct TRAIT_NAME : std::false_type {}; / template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

lo que se hace arriba es crear una macro MAKE_CODE_TRAIT . Le pasa el nombre del rasgo que desea y un código que puede probar el tipo T Así:

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

crea la clase de rasgos anteriores.

Además, la técnica anterior es parte de lo que MS llama "expresión SFINAE", y su compilador de 2013 falla bastante.

Tenga en cuenta que en C ++ 1y la siguiente sintaxis es posible:

template<class T> std::string optionalToString(T* obj) { return compiled_if< has_to_string >(*obj, [&](auto&& obj) { return obj.toString(); }) *compiled_else ([&]{ return "toString not defined"; }); }

que es una rama condicional de compilación en línea que abusa de muchas características de C ++. Probablemente no valga la pena hacerlo, ya que el beneficio (de que el código esté en línea) no vale la pena el costo (de casi nadie entiende cómo funciona), pero la existencia de esa solución anterior puede ser interesante.


Esta pregunta es antigua, pero con C ++ 11 tenemos una nueva forma de verificar la existencia de una función (o la existencia de cualquier miembro que no sea de tipo, en realidad), confiando nuevamente en SFINAE:

template<class T> auto serialize_imp(std::ostream& os, T const& obj, int) -> decltype(os << obj, void()) { os << obj; } template<class T> auto serialize_imp(std::ostream& os, T const& obj, long) -> decltype(obj.stream(os), void()) { obj.stream(os); } template<class T> auto serialize(std::ostream& os, T const& obj) -> decltype(serialize_imp(os, obj, 0), void()) { serialize_imp(os, obj, 0); }

Ahora en algunas explicaciones. En primer lugar, uso la expresión SFINAE para excluir las funciones de serialize(_imp) de la resolución de sobrecarga, si la primera expresión dentro de decltype no es válida (también conocida como la función no existe).

El void() se utiliza para void() el tipo de retorno de todas esas funciones.

El argumento 0 se usa para preferir la sobrecarga os << obj si ambos están disponibles (el literal 0 es de tipo int y, como tal, la primera sobrecarga es una mejor coincidencia).

Ahora, probablemente desee un rasgo para verificar si existe una función. Por suerte, es fácil escribir eso. Sin embargo, tenga en cuenta que necesita escribir un rasgo por cada nombre de función diferente que desee.

#include <type_traits> template<class> struct sfinae_true : std::true_type{}; namespace detail{ template<class T, class A0> static auto test_stream(int) -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>; template<class, class A0> static auto test_stream(long) -> std::false_type; } // detail:: template<class T, class Arg> struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

Ejemplo vivo.

Y a las explicaciones. Primero, sfinae_true es un tipo de ayuda, y básicamente equivale a escribir decltype(void(std::declval<T>().stream(a0)), std::true_type{}) . La ventaja es simplemente que es más corto.
A continuación, la struct has_stream : decltype(...) hereda de std::true_type o std::false_type al final, dependiendo de si la comprobación de test_stream en test_stream falla o no.
Por último, std::declval te da un "valor" del tipo que pases, sin que necesites saber cómo puedes construirlo. Tenga en cuenta que esto solo es posible dentro de un contexto no evaluado, como decltype , sizeof y otros.

Tenga en cuenta que decltype no es necesariamente necesario, ya que sizeof (y todos los contextos no evaluados) obtuvieron esa mejora. Es solo que decltype ya entrega un tipo y como tal es simplemente más limpio. Aquí hay una versión sizeof de una de las sobrecargas:

template<class T> void serialize_imp(std::ostream& os, T const& obj, int, int(*)[sizeof((os << obj),0)] = 0) { os << obj; }

Los parámetros int y long siguen allí por la misma razón. El puntero de matriz se usa para proporcionar un contexto donde se puede usar sizeof .


La solución estándar de C ++ presentada aquí por litb no funcionará como se espera si el método se define en una clase base.

Para una solución que maneja esta situación refiérase a:

En ruso: http://www.rsdn.ru/forum/message/2759773.1.aspx

Traducción al inglés por Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

Es increíblemente inteligente. Sin embargo, un problema con esta solución es que da errores de compilación si el tipo que se está probando es uno que no se puede usar como clase base (por ejemplo, tipos primitivos)

En Visual Studio, noté que si se trabaja con un método que no tiene argumentos, se debe insertar un par adicional de redundant () alrededor de los argumentos para deducir () en la expresión sizeof.


Para eso están los rasgos de los tipos. Desafortunadamente, tienen que ser definidos manualmente. En tu caso, imagina lo siguiente:

template <typename T> struct response_trait { static bool const has_tostring = false; }; template <> struct response_trait<your_type_with_tostring> { static bool const has_tostring = true; }


Sí, con SFINAE puede verificar si una clase dada proporciona un método determinado. Aquí está el código de trabajo:

#include <iostream> struct Hello { int helloworld() { return 0; } }; struct Generic {}; // SFINAE test template <typename T> class has_helloworld { typedef char one; typedef long two; template <typename C> static one test( typeof(&C::helloworld) ) ; template <typename C> static two test(...); public: enum { value = sizeof(test<T>(0)) == sizeof(char) }; }; int main(int argc, char *argv[]) { std::cout << has_helloworld<Hello>::value << std::endl; std::cout << has_helloworld<Generic>::value << std::endl; return 0; }

Acabo de probarlo con Linux y gcc 4.1 / 4.3. No sé si es portátil a otras plataformas que ejecutan compiladores diferentes.


Una solución simple para C ++ 11:

template<class T> auto optionalToString(T* obj) -> decltype( obj->toString() ) { return obj->toString(); } auto optionalToString(...) -> string { return "toString not defined"; }

Actualización, 3 años después: (y esto no está probado). Para probar la existencia, creo que esto funcionará:

template<class T> constexpr auto test_has_toString_method(T* obj) -> decltype( obj->toString() , std::true_type{} ) { return obj->toString(); } constexpr auto test_has_toString_method(...) -> std::false_type { return "toString not defined"; }


¿Qué tal esta solución?

#include <type_traits> template <typename U, typename = void> struct hasToString : std::false_type { }; template <typename U> struct hasToString<U, typename std::enable_if<bool(sizeof(&U::toString))>::type > : std::true_type { };


Aquí hay un ejemplo del código de trabajo.

template<typename T> using toStringFn = decltype(std::declval<const T>().toString()); template <class T, toStringFn<T>* = nullptr> std::string optionalToString(const T* obj, int) { return obj->toString(); } template <class T> std::string optionalToString(const T* obj, long) { return "toString not defined"; } int main() { A* a; B* b; std::cout << optionalToString(a, 0) << std::endl; // This is A std::cout << optionalToString(b, 0) << std::endl; // toString not defined }

toStringFn<T>* = nullptrhabilitará la función que toma intargumentos adicionales que tienen una prioridad sobre la función que toma longcuando se llama con 0.

Puede usar el mismo principio para las funciones que devuelve truesi la función está implementada.

template <typename T> constexpr bool toStringExists(long) { return false; } template <typename T, toStringFn<T>* = nullptr> constexpr bool toStringExists(int) { return true; } int main() { A* a; B* b; std::cout << toStringExists<A>(0) << std::endl; // true std::cout << toStringExists<B>(0) << std::endl; // false }


Escribí una respuesta a esto en otro hilo que (a diferencia de las soluciones anteriores) también verifica las funciones de los miembros heredados:

SFINAE para verificar funciones miembro heredadas

Aquí hay algunos ejemplos de esa solución:

Ejemplo 1:

Estamos buscando un miembro con la siguiente firma: T::const_iterator begin() const

template<class T> struct has_const_begin { typedef char (&Yes)[1]; typedef char (&No)[2]; template<class U> static Yes test(U const * data, typename std::enable_if<std::is_same< typename U::const_iterator, decltype(data->begin()) >::value>::type * = 0); static No test(...); static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0)); };

Tenga en cuenta que incluso comprueba la constancia del método y también funciona con tipos primitivos. (Quiero decir que has_const_begin<int>::valuees falso y no causa un error en tiempo de compilación).

Ejemplo 2

Ahora estamos buscando la firma: void foo(MyClass&, unsigned)

template<class T> struct has_foo { typedef char (&Yes)[1]; typedef char (&No)[2]; template<class U> static Yes test(U * data, MyClass* arg1 = 0, typename std::enable_if<std::is_void< decltype(data->foo(*arg1, 1u)) >::value>::type * = 0); static No test(...); static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0)); };

Tenga en cuenta que MyClass no tiene que ser construible por defecto o para satisfacer ningún concepto especial. La técnica también funciona con miembros de la plantilla.

Estoy esperando ansiosamente opiniones sobre esto.


Extraño, nadie sugirió el siguiente buen truco que vi una vez en este mismo sitio:

template <class T> struct has_foo { struct S { void foo(...); }; struct derived : S, T {}; template <typename V, V> struct W {}; template <typename X> char (&test(W<void (X::*)(), &X::foo> *))[1]; template <typename> char (&test(...))[2]; static const bool value = sizeof(test<derived>(0)) == 1; };

Tienes que asegurarte de que T es una clase. Parece que la ambigüedad en la búsqueda de foo es un fallo de sustitución. Lo hice funcionar en gcc, aunque no estoy seguro de si es estándar.


La plantilla genérica que se puede usar para verificar si alguna "característica" es compatible con el tipo:

#include <type_traits> template <template <typename> class TypeChecker, typename Type> struct is_supported { // these structs are used to recognize which version // of the two functions was chosen during overload resolution struct supported {}; struct not_supported {}; // this overload of chk will be ignored by SFINAE principle // if TypeChecker<Type_> is invalid type template <typename Type_> static supported chk(typename std::decay<TypeChecker<Type_>>::type *); // ellipsis has the lowest conversion rank, so this overload will be // chosen during overload resolution only if the template overload above is ignored template <typename Type_> static not_supported chk(...); // if the template overload of chk is chosen during // overload resolution then the feature is supported // if the ellipses overload is chosen the the feature is not supported static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value; };

La plantilla que verifica si existe un método fooque sea compatible con la firma.double(const char*)

// if T doesn''t have foo method with the signature that allows to compile the bellow // expression then instantiating this template is Substitution Failure (SF) // which Is Not An Error (INAE) if this happens during overload resolution template <typename T> using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));

Ejemplos

// types that support has_foo struct struct1 { double foo(const char*); }; // exact signature match struct struct2 { int foo(const std::string &str); }; // compatible signature struct struct3 { float foo(...); }; // compatible ellipsis signature struct struct4 { template <typename T> int foo(T t); }; // compatible template signature // types that do not support has_foo struct struct5 { void foo(const char*); }; // returns void struct struct6 { std::string foo(const char*); }; // std::string can''t be converted to double struct struct7 { double foo( int *); }; // const char* can''t be converted to int* struct struct8 { double bar(const char*); }; // there is no foo method int main() { std::cout << std::boolalpha; std::cout << is_supported<has_foo, int >::value << std::endl; // false std::cout << is_supported<has_foo, double >::value << std::endl; // false std::cout << is_supported<has_foo, struct1>::value << std::endl; // true std::cout << is_supported<has_foo, struct2>::value << std::endl; // true std::cout << is_supported<has_foo, struct3>::value << std::endl; // true std::cout << is_supported<has_foo, struct4>::value << std::endl; // true std::cout << is_supported<has_foo, struct5>::value << std::endl; // false std::cout << is_supported<has_foo, struct6>::value << std::endl; // false std::cout << is_supported<has_foo, struct7>::value << std::endl; // false std::cout << is_supported<has_foo, struct8>::value << std::endl; // false return 0; }

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4


Ahora este era un bonito y pequeño rompecabezas, ¡gran pregunta!

Aquí hay una alternativa a la solución de Nicola Bonelli que no se basa en el typeofoperador no estándar .

Desafortunadamente, no funciona en GCC (MinGW) 3.4.5 o Digital Mars 8.42n, pero funciona en todas las versiones de MSVC (incluido VC6) y en Comeau C ++.

El bloque de comentarios más largo tiene los detalles sobre cómo funciona (o se supone que funciona). Como dice, no estoy seguro de qué comportamiento cumple con las normas; me gustaría recibir comentarios sobre eso.

actualización - 7 de noviembre de 2008:

Parece que mientras este código es sintácticamente correcto, el comportamiento que muestran MSVC y Comeau C ++ no sigue el estándar (gracias a Leon Timmermans y litb por litb en la dirección correcta). El estándar C ++ 03 dice lo siguiente:

14.6.2 Nombres dependientes [temp.dep]

Parrafo 3

En la definición de una plantilla de clase o un miembro de una plantilla de clase, si una clase base de la plantilla de clase depende de un parámetro de plantilla, el alcance de la clase base no se examina durante la búsqueda de nombre no calificado en el punto de definición de la clase plantilla o miembro o durante una instanciación de la plantilla de clase o miembro.

Entonces, parece que cuando MSVC o Comeau consideran la toString()función miembro de Trealizar una búsqueda de nombre en el sitio de llamada doToString()cuando se crea una instancia de la plantilla, eso es incorrecto (aunque en realidad es el comportamiento que estaba buscando en este caso).

El comportamiento de GCC y Digital Mars parece ser correcto: en ambos casos, la función no miembro toString()está vinculada a la llamada.

Ratas: pensé que podría haber encontrado una solución inteligente, en lugar de eso descubrí un par de errores del compilador ...

#include <iostream> #include <string> struct Hello { std::string toString() { return "Hello"; } }; struct Generic {}; // the following namespace keeps the toString() method out of // most everything - except the other stuff in this // compilation unit namespace { std::string toString() { return "toString not defined"; } template <typename T> class optionalToStringImpl : public T { public: std::string doToString() { // in theory, the name lookup for this call to // toString() should find the toString() in // the base class T if one exists, but if one // doesn''t exist in the base class, it''ll // find the free toString() function in // the private namespace. // // This theory works for MSVC (all versions // from VC6 to VC9) and Comeau C++, but // does not work with MinGW 3.4.5 or // Digital Mars 8.42n // // I''m honestly not sure what the standard says // is the correct behavior here - it''s sort // of like ADL (Argument Dependent Lookup - // also known as Koenig Lookup) but without // arguments (except the implied "this" pointer) return toString(); } }; } template <typename T> std::string optionalToString(T & obj) { // ugly, hacky cast... optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj); return temp->doToString(); } int main(int argc, char *argv[]) { Hello helloObj; Generic genericObj; std::cout << optionalToString( helloObj) << std::endl; std::cout << optionalToString( genericObj) << std::endl; return 0; }


Aquí está mi versión que maneja todas las posibles sobrecargas de funciones miembro con aridades arbitrarias, incluidas las funciones de miembros de plantilla, posiblemente con argumentos predeterminados. Distingue 3 escenarios mutuamente excluyentes al realizar una llamada de función miembro a algún tipo de clase, con los tipos de argumento dados: (1) válido, o (2) ambiguo, o (3) no viable. Ejemplo de uso:

#include <string> #include <vector> HAS_MEM(bar) HAS_MEM_FUN_CALL(bar) struct test { void bar(int); void bar(double); void bar(int,double); template < typename T > typename std::enable_if< not std::is_integral<T>::value >::type bar(const T&, int=0){} template < typename T > typename std::enable_if< std::is_integral<T>::value >::type bar(const std::vector<T>&, T*){} template < typename T > int bar(const std::string&, int){} };

Ahora puedes usarlo así:

int main(int argc, const char * argv[]) { static_assert( has_mem_bar<test>::value , ""); static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , ""); static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , ""); static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , ""); static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , ""); static_assert( has_valid_mem_fun_call_bar<test(int)>::value , ""); static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , ""); static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , ""); static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , ""); static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , ""); static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , ""); static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , ""); static_assert( has_viable_mem_fun_call_bar<test(int)>::value , ""); static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , ""); return 0; }

Aquí está el código, escrito en c ++ 11, sin embargo, puede transportarlo fácilmente (con pequeños ajustes) a no-c ++ 11 que tenga extensiones de tipo de (por ejemplo, gcc). Puede reemplazar la macro HAS_MEM con la suya.

#pragma once #if __cplusplus >= 201103 #include <utility> #include <type_traits> #define HAS_MEM(mem) / / template < typename T > / struct has_mem_##mem / { / struct yes {}; / struct no {}; / / struct ambiguate_seed { char mem; }; / template < typename U > struct ambiguate : U, ambiguate_seed {}; / / template < typename U, typename = decltype(&U::mem) > static constexpr no test(int); / template < typename > static constexpr yes test(...); / / static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ; / typedef std::integral_constant<bool,value> type; / }; #define HAS_MEM_FUN_CALL(memfun) / / template < typename Signature > / struct has_valid_mem_fun_call_##memfun; / / template < typename T, typename... Args > / struct has_valid_mem_fun_call_##memfun< T(Args...) > / { / struct yes {}; / struct no {}; / / template < typename U, bool = has_mem_##memfun<U>::value > / struct impl / { / template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > / struct test_result { using type = yes; }; / / template < typename V > static constexpr typename test_result<V>::type test(int); / template < typename > static constexpr no test(...); / / static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value; / using type = std::integral_constant<bool, value>; / }; / / template < typename U > / struct impl<U,false> : std::false_type {}; / / static constexpr bool value = impl<T>::value; / using type = std::integral_constant<bool, value>; / }; / / template < typename Signature > / struct has_ambiguous_mem_fun_call_##memfun; / / template < typename T, typename... Args > / struct has_ambiguous_mem_fun_call_##memfun< T(Args...) > / { / struct ambiguate_seed { void memfun(...); }; / / template < class U, bool = has_mem_##memfun<U>::value > / struct ambiguate : U, ambiguate_seed / { / using ambiguate_seed::memfun; / using U::memfun; / }; / / template < class U > / struct ambiguate<U,false> : ambiguate_seed {}; / / static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; / using type = std::integral_constant<bool, value>; / }; / / template < typename Signature > / struct has_viable_mem_fun_call_##memfun; / / template < typename T, typename... Args > / struct has_viable_mem_fun_call_##memfun< T(Args...) > / { / static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value / or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value; / using type = std::integral_constant<bool, value>; / }; / / template < typename Signature > / struct has_no_viable_mem_fun_call_##memfun; / / template < typename T, typename... Args > / struct has_no_viable_mem_fun_call_##memfun < T(Args...) > / { / static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value; / using type = std::integral_constant<bool, value>; / }; / / template < typename Signature > / struct result_of_mem_fun_call_##memfun; / / template < typename T, typename... Args > / struct result_of_mem_fun_call_##memfun< T(Args...) > / { / using type = decltype(std::declval<T>().memfun(std::declval<Args>()...)); / }; #endif


Aquí hay muchas respuestas, pero no pude encontrar una versión que realice el ordenamiento de la resolución del método real , sin utilizar ninguna de las funciones más nuevas de c ++ (solo las funciones de c ++ 98).
Nota: esta versión está probada y funciona con vc ++ 2013, g ++ 5.2.0 y el compilador en línea.

Entonces se me ocurrió una versión, que solo usa sizeof ():

template<typename T> T declval(void); struct fake_void { }; template<typename T> T &operator,(T &,fake_void); template<typename T> T const &operator,(T const &,fake_void); template<typename T> T volatile &operator,(T volatile &,fake_void); template<typename T> T const volatile &operator,(T const volatile &,fake_void); struct yes { char v[1]; }; struct no { char v[2]; }; template<bool> struct yes_no:yes{}; template<> struct yes_no<false>:no{}; template<typename T> struct has_awesome_member { template<typename U> static yes_no<(sizeof(( declval<U>().awesome_member(),fake_void() ))!=0)> check(int); template<typename> static no check(...); enum{value=sizeof(check<T>(0)) == sizeof(yes)}; }; struct foo { int awesome_member(void); }; struct bar { }; struct foo_void { void awesome_member(void); }; struct wrong_params { void awesome_member(int); }; static_assert(has_awesome_member<foo>::value,""); static_assert(!has_awesome_member<bar>::value,""); static_assert(has_awesome_member<foo_void>::value,""); static_assert(!has_awesome_member<wrong_params>::value,"");

Demostración en vivo (con comprobación de tipo de retorno extendido y solución alternativa de vc ++ 2010): http://cpp.sh/5b2vs

Ninguna fuente, ya que se me ocurrió.

Al ejecutar la demostración en vivo en el compilador g ++, tenga en cuenta que se permiten tamaños de matriz de 0, lo que significa que el static_assert utilizado no generará un error de compilador, incluso cuando falle.
Una solución alternativa utilizada comúnmente es reemplazar ''typedef'' en la macro con ''extern''.


MSVC tiene las palabras clave __if_exists y __if_not_exists ( Doc ). Junto con el enfoque tipo de SFINAE de Nicola, pude crear un cheque para GCC y MSVC como el OP buscado.

Actualización: La fuente se puede encontrar Here


Modifiqué la solución provista en https://.com/a/264088/2712152 para hacerlo un poco más general. Además, como no usa ninguna de las nuevas características de C ++ 11, podemos usarlo con compiladores antiguos y también debería funcionar con msvc. Pero los compiladores deben permitir que C99 use esto ya que usa macros variadic.

La siguiente macro se puede usar para verificar si una clase en particular tiene o no un typedef en particular.

/** * @class : HAS_TYPEDEF * @brief : This macro will be used to check if a class has a particular * typedef or not. * @param typedef_name : Name of Typedef * @param name : Name of struct which is going to be run the test for * the given particular typedef specified in typedef_name */ #define HAS_TYPEDEF(typedef_name, name) / template <typename T> / struct name { / typedef char yes[1]; / typedef char no[2]; / template <typename U> / struct type_check; / template <typename _1> / static yes& chk(type_check<typename _1::typedef_name>*); / template <typename> / static no& chk(...); / static bool const value = sizeof(chk<T>(0)) == sizeof(yes); / }

La siguiente macro se puede usar para verificar si una clase en particular tiene una función miembro en particular o no con un número dado de argumentos.

/** * @class : HAS_MEM_FUNC * @brief : This macro will be used to check if a class has a particular * member function implemented in the public section or not. * @param func : Name of Member Function * @param name : Name of struct which is going to be run the test for * the given particular member function name specified in func * @param return_type: Return type of the member function * @param ellipsis(...) : Since this is macro should provide test case for every * possible member function we use variadic macros to cover all possibilities */ #define HAS_MEM_FUNC(func, name, return_type, ...) / template <typename T> / struct name { / typedef return_type (T::*Sign)(__VA_ARGS__); / typedef char yes[1]; / typedef char no[2]; / template <typename U, U> / struct type_check; / template <typename _1> / static yes& chk(type_check<Sign, &_1::func>*); / template <typename> / static no& chk(...); / static bool const value = sizeof(chk<T>(0)) == sizeof(yes); / }

Podemos usar las 2 macros anteriores para realizar las comprobaciones de has_typedef y has_mem_func como:

class A { public: typedef int check; void check_function() {} }; class B { public: void hello(int a, double b) {} void hello() {} }; HAS_MEM_FUNC(check_function, has_check_function, void, void); HAS_MEM_FUNC(hello, hello_check, void, int, double); HAS_MEM_FUNC(hello, hello_void_check, void, void); HAS_TYPEDEF(check, has_typedef_check); int main() { std::cout << "Check Function A:" << has_check_function<A>::value << std::endl; std::cout << "Check Function B:" << has_check_function<B>::value << std::endl; std::cout << "Hello Function A:" << hello_check<A>::value << std::endl; std::cout << "Hello Function B:" << hello_check<B>::value << std::endl; std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl; std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl; std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl; std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl; }


Puede omitir toda la metaprogramación en C ++ 14, y simplemente escribir esto usando fit::conditionalla biblioteca Fit :

template<class T> std::string optionalToString(T* x) { return fit::conditional( [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); }, [](auto*) { return "toString not defined"; } )(x); }

También puede crear la función directamente desde las lambdas:

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional( [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); }, [](auto*) -> std::string { return "toString not defined"; } );

Sin embargo, si está utilizando un compilador que no admite lambdas genéricas, tendrá que escribir objetos de función separados:

struct withToString { template<class T> auto operator()(T* obj) const -> decltype(obj->toString(), std::string()) { return obj->toString(); } }; struct withoutToString { template<class T> std::string operator()(T*) const { return "toString not defined"; } }; FIT_STATIC_FUNCTION(optionalToString) = fit::conditional( withToString(), withoutToString() );


Un ejemplo que utiliza SFINAE y la especialización parcial de la plantilla, escribiendo una Has_fooverificación de concepto:

#include <type_traits> struct A{}; struct B{ int foo(int a, int b);}; struct C{void foo(int a, int b);}; struct D{int foo();}; struct E: public B{}; // available in C++17 onwards as part of <type_traits> template<typename...> using void_t = void; template<typename T, typename = void> struct Has_foo: std::false_type{}; template<typename T> struct Has_foo<T, void_t< std::enable_if_t< std::is_same< int, decltype(std::declval<T>().foo((int)0, (int)0)) >::value > >>: std::true_type{}; static_assert(not Has_foo<A>::value, "A does not have a foo"); static_assert(Has_foo<B>::value, "B has a foo"); static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. "); static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. "); static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");