¿Verificación de la existencia de una función miembro de clase?


499

¿Es posible escribir una plantilla que cambie el comportamiento dependiendo de si una determinada función miembro 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 class Tha toString()definido, entonces lo usa; de lo contrario, no lo hace. La parte mágica que no sé cómo hacer es la parte "FUNCTION_EXISTS".


66
Por supuesto, no hace falta decir que las respuestas de la plantilla a continuación solo funcionan con información en tiempo de compilación, es decir, T debe tener toString. Si se pasa en una subclase de T que hace definir toString, pero T todavía no , se le informará toString no está definido.
Alice Purcell

Respuestas:


319

Sí, con SFINAE puede verificar si una clase determinada 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;
    struct two { char x[2]; };

    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;
}

Lo acabo de probar con Linux y gcc 4.1 / 4.3. No sé si es portátil para otras plataformas que ejecutan compiladores diferentes.


18
Aunque, utilicé lo siguiente para 'uno' y 'dos': typedef char Small; class Big {char dummy [2];} para garantizar que no haya ambigüedad sobre el tamaño variable dependiente de la plataforma.
user23167

66
Dudo que exista en la tierra una plataforma con sizeof (char) == sizeof (long)
Nicola Bonelli

17
No estoy completamente seguro, pero no creo que sea portátil. typeof es una extensión GCC, esto no funcionará en otros compiladores.
Leon Timmermans el

56
typeof no es necesario - char [sizeof (& C :: helloworld)] también funciona. Y para evitar sizeof (long) == sizeof (char), use una struct {char [2]} ;. Debe tener un tamaño> = 2
MSalters

57
Trivial, pero me tomó un tiempo para averiguar: sustituir typeofpor decltypeal usar C ++ 0x , por ejemplo, a través de -std = C ++ 0x.
hrr

265

Esta pregunta es antigua, pero con C ++ 11 tenemos una nueva forma de verificar la existencia de funciones (o la existencia de cualquier miembro que no sea de tipo, realmente), 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 sobre algunas explicaciones. Lo primero, uso la expresión SFINAE para excluir las serialize(_imp)funciones de la resolución de sobrecarga, si la primera expresión dentro decltypeno es válida (también conocida, la función no existe).

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

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


Ahora, probablemente desee un rasgo para verificar si existe una función. Afortunadamente, es fácil escribir eso. Sin embargo, tenga en cuenta que debe escribir un rasgo usted mismo para 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 en vivo.

Y a las explicaciones. Primero, sfinae_truees un tipo auxiliar, y básicamente equivale a escribir decltype(void(std::declval<T>().stream(a0)), std::true_type{}). La ventaja es simplemente que es más corta.
A continuación, struct has_stream : decltype(...)hereda de cualquiera std::true_typeo std::false_typeal final, dependiendo de si el decltyperegistro test_streamfalla o no.
Por último, std::declvalle da un "valor" de cualquier tipo que pase, sin que necesite saber cómo puede construirlo. Tenga en cuenta que esto sólo es posible dentro de un contexto sin evaluar, tales como decltype, sizeofentre otros.


Tenga en cuenta que decltypeno es necesariamente necesario, ya que sizeof(y todos los contextos no evaluados) obtuvieron esa mejora. Es solo que decltypeya ofrece un tipo y, como tal, es más limpio. Aquí hay una sizeofversión 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 inty longtodavía están allí por la misma razón. El puntero de matriz se usa para proporcionar un contexto donde sizeofse puede usar.


44
La ventaja de decltypeover sizeoftambién es que las reglas especialmente diseñadas para las llamadas a funciones no introducen un temporal (por lo que no tiene que tener derechos de acceso al destructor del tipo de retorno y no causará una instanciación implícita si el tipo de retorno es una instancia de plantilla de clase).
Johannes Schaub - litb

55
Microsoft aún no ha implementado Expression SFINAE en su compilador C ++. Solo imagino que podría ayudar a ahorrar tiempo a algunas personas, ya que estaba confundido por qué esto no me funcionaba. Buena solución, ¡no puedo esperar para usarlo en Visual Studio!
Jonathan

3
Su primer enlace de ejemplo está roto
NathanOliver

1
Hay que decir que static_assert(has_stream<X, char>() == true, "fail X");se compilará y no se afirmará porque char se puede convertir a int, por lo que si ese comportamiento no se desea y se desea que todos los tipos de argumentos coincidan, no sé cómo se puede lograr.
Gabriel

44
Si está tan perplejo como yo sobre los dos argumentos para decltype: decltype realmente solo toma uno; La coma es un operador aquí. Ver stackoverflow.com/questions/16044514/…
André

159

C ++ permite que se use SFINAE para esto (tenga en cuenta que con las características de C ++ 11 esto es más simple porque admite SFINAE extendido en expresiones casi arbitrarias; lo siguiente fue diseñado 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 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 se ignore la plantilla. 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 toStringfunción en ese if branch. dado que 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::*)() constcomo tipo de puntero de función miembro entonces!).


77
Me gusta cómo type_checkse usa para garantizar que las firmas coincidan exactamente. ¿Hay alguna manera de hacerlo para que coincida con cualquier método que pueda llamarse de la misma manera que Signpodría llamarse un método con firma ? (Por ejemplo, si Sign= std::string(T::*)(), permitir std::string T::toString(int default = 42, ...)que coincida.)
j_random_hacker

55
Simplemente descubro algo sobre esto que no era obvio para mí, así que en caso de que ayude a otros: ¡chk no es y no necesita ser definido! El operador sizeof determina el tamaño de la salida de chk sin necesidad de llamar a chk.
SCFrench

3
@ deek0146: Sí, Tno debe ser un tipo primitivo, porque la declaración de puntero a método de T no está sujeta a SFINAE y generará un error para cualquier T. IMO que no sea de clase. La solución más fácil es combinar con el is_classcheque de aumentar.
Jan Hudec

2
¿Cómo puedo hacer que esto funcione si mi toStringes una función con plantilla?
Frank

44
¿Es esto (o algo equivalente) en Boost?
Dan Nissenbaum

89

C ++ 20 - requiresexpresiones

Con C ++ 20 vienen conceptos y herramientas variadas como requiresexpresiones que son una forma integrada de verificar la existencia de una función. Con ellos, podría reescribir su optionalToStringfunción de la siguiente manera:

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

Pre-C ++ 20 - Kit de herramientas de detección

N4502 propone un kit de herramientas de detección para su inclusión en la biblioteca estándar C ++ 17 que finalmente se convirtió en los fundamentos de la biblioteca TS v2. Lo más probable es que nunca llegue al estándar porque ha sido subsumido por requiresexpresiones desde entonces, pero aún resuelve el problema de una manera elegante. El kit de herramientas presenta algunas metafunciones, incluidas las std::is_detectedque se pueden usar para escribir fácilmente metafunciones de detección de tipo o función en la parte superior. Así es como podría 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 se ha 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. Funciona bien con la función 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";
}

C ++ 14 - Boost.Hana

Boost.Hana aparentemente se basa en este ejemplo específico y proporciona una solución para C ++ 14 en su documentación, por lo que voy a citarlo directamente:

[...] Hana proporciona una is_validfunción que se puede combinar con lambdas genéricas C ++ 14 para obtener una implementación mucho más limpia de lo mismo:

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

Esto nos deja con un objeto de función has_toStringque devuelve si la expresión dada es válida en el argumento que le pasamos. El resultado se devuelve como un IntegralConstant, por lo que constexpr-ness no es un problema aquí porque el resultado de la función se representa como un tipo de todos modos. Ahora, además de ser menos detallado (¡eso es un trazador de líneas!), La intención es mucho más clara. Otros beneficios son el hecho de que has_toStringse puede pasar a algoritmos de orden superior y también se puede definir en el alcance de la función, por lo que no es necesario contaminar el alcance del espacio de nombres con detalles de implementación.

Boost.TTI

Otro kit de herramientas algo idiomático para realizar dicha verificación, aunque menos elegante, es Boost.TTI , presentado en Boost 1.54.0. Para su ejemplo, tendría que usar la macro BOOST_TTI_HAS_MEMBER_FUNCTION. Así es como podría 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 boolpara crear un cheque SFINAE.

Explicación

La macro BOOST_TTI_HAS_MEMBER_FUNCTIONgenera la metafunción has_member_function_toStringque toma el tipo marcado 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 miembro valuecontiene truesi la clase Ttiene una función miembro std::string toString().

Alternativamente, has_member_function_toStringpuede 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>::valuepor has_member_function_toString<std::string T::* ()>::value.


1
más conciso que 03
ZFY

@ZFY Creo que Boost.TTI también funciona con C ++ 03, pero es la solución menos elegante del lote.
Morwenn

¿Es realmente válida la solución C ++ 20? Me gustaría, pero g ++ y msvc lo rechazan, solo aceptado por clang.
Bernd Baumanns

en cppreference puede leer: Si una expresión obligatoria contiene tipos o expresiones inválidas en sus requisitos, y no aparece dentro de la declaración de una entidad con plantilla, entonces el programa está mal formado.
Bernd Baumanns

@BerndBaumanns ¿En serio? Lo hice funcionar con el enlace troncal de GCC: godbolt.org/z/CBwZdE Quizás tengas razón, solo verifiqué que funcionara pero no verifiqué si era legal de acuerdo con la redacción estándar.
Morwenn

56

Aunque esta pregunta tiene dos años, me atreveré a agregar mi respuesta. Esperemos que aclare la solución previa, 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 typeofextensión:

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 revisé con gcc 4.1.2. El crédito va principalmente a Nicola Bonelli y Johannes Schaub, así que denles un voto si mi respuesta lo ayuda :)


1
Solo me pregunto, ¿esto hace algo que la solución de Konrad Rudolph a continuación no hace?
Alastair Irvine

3
@AlastairIrvine, esta solución oculta toda la lógica interna, Konrad pone parte de la carga en el usuario. Aunque es breve y mucho más legible, la solución de Konrad requiere una especialización de plantilla separada para cada clase que lo tenga toString. Si escribe una biblioteca genérica, que desea trabajar con cualquier clase (piense en algo como impulso), entonces requerir que el usuario defina especializaciones adicionales de algunas plantillas oscuras puede ser inaceptable. A veces es preferible escribir un código muy complicado para mantener la interfaz pública lo más simple posible.
FireAphis

30

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 se ha 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";
}

44
Esto es simple y elegante, pero estrictamente hablando no responde la pregunta de OP: no habilita a la persona que llama para verificar la existencia de una función, siempre la proporciona . Pero bueno de todos modos.
Adrian W

@AdrianW, buen punto. He actualizado mi respuesta. Aunque no lo he probado
Aaron McDaid

En caso de que ayude a alguien más, no podría hacer que esto funcione template<typename>antes de la sobrecarga variable: no se estaba considerando para su resolución.
Laboratorio Cobotica

De nuevo, esto no es válido C ++ 11.
Peter

29

Para eso están los rasgos de tipo. Desafortunadamente, tienen que definirse manualmente. En su caso, imagine 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;
}

55
debería preferir enum para rasgos en lugar de constantes estáticas: "Los miembros constantes estáticos son valores, lo que obliga al compilador a crear instancias y asignar la definición para el miembro estático. Como resultado, el cálculo ya no se limita a un" tiempo de compilación puro " "efecto".
Özgür

55
"Los valores de enumeración no son valores (es decir, no tienen una dirección). Entonces, cuando los pasa" por referencia ", no se usa memoria estática. Es casi exactamente como si pasara el valor calculado como un literal . Estas consideraciones nos motivan a usar valores de enumeración "Plantillas C ++: La Guía Completa
Özgür

22
Comptrol: no, el pasaje citado no se aplica aquí ya que las constantes estáticas de tipo entero son un caso especial. Aquí se comportan exactamente como una enumeración y son la forma preferida. El viejo truco de enumeración solo era necesario en compiladores que no seguían el estándar C ++.
Konrad Rudolph el

3
@Roger Pate: No del todo. "Usado en el programa" aquí aparentemente es sinónimo de "referenciado". La lectura predominante de este pasaje, y el implementado por todos los compiladores de C ++ modernos, es que puede tomar el valor de una constante estática sin necesidad de declararlo (la oración anterior dice esto: “... el miembro puede aparecer en expresiones constantes integrales ... "). Usted solamente tiene que definir si se toma su dirección (de forma explícita a través de &T::xo implícitamente mediante la unión a una referencia).
Konrad Rudolph


25

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 lo hace mucho más simple. Vea N4502 para más detalles, pero como ejemplo autónomo considere lo siguiente.

Esta parte es la parte constante, póngala 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 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_ty detect) es suficiente, sin necesidad de macros peludas. Además, se informó (ver N4502 ) que es mediblemente 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 causó void_tque 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://stackoverflow.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

¿Es posible extenderlo para detectar funciones que no sean miembros?
plasmacel

Si seguro. Mire cuidadosamente los ejemplos: básicamente proporciona una expresión y verifica si es válida. Nada requiere que esta expresión sea solo sobre una llamada de función miembro.
akim

N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf ) es el camino hacia el futuro ... Estaba buscando una forma ordenada de detectar cosas en tipos y N4502 es el camino ir.
tlonuk

11

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 {};

Rasgo has_to_stringde tal manera que has_to_string<T>::valuees truesi y sólo si Ttiene un método .toStringque se puede invocar 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 complejas expresiones SFINAE.

Puedes escribir estos rasgos con una macro si te encuentras 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 hace lo anterior 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 anterior.

Como comentario aparte, 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 es posible la siguiente sintaxis:

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 el costo (de que casi nadie entienda cómo funciona), pero la existencia de esa solución anterior puede ser de interés.


¿Esto maneja casos privados?
tower120

@ tower120 Tendría que experimentar: cómo las plantillas interactúan con privado / público / protegido es un poco oscuro para mí. Sin has_to_stringembargo , no importará dónde invoque .
Yakk - Adam Nevraumont

pero ya sabes, si miras desde el otro lado ... Podemos llegar a los miembros protegidos de la clase Derivada. Tal vez si coloca todo esto dentro de la clase, y convierte de structs a funciones constexpr ...
tower120

Aquí, mira este coliru.stacked-crooked.com/a/ee94d16e7c07e093 No puedo hacerlo constexpr
tower120


10

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

Verifica si hay un miembro xen una clase determinada. 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;

Verifique la función del 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;

Verifique 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;

Verifique la unión de miembros x:

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

Verifique 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;

Verifique cualquier función de miembro xindependientemente 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)

1
¿Tiene alguna idea de por qué si cambiamos sig_check<func_sig, &T::func_name>a verificación de función libre: sig_check<func_sig, &func_name>no se puede construir con un "identificador no declarado" que menciona el nombre de la función que queremos verificar? porque esperaría que SFINAE no lo convierta en un error, solo hace eso para los miembros, ¿por qué no para funciones gratuitas?
v.oddou

Supongo que tendría algo que ver con el hecho de que una función libre no es una clase o estructura. Esta técnica de deducir la presencia de un miembro realmente se centra en el mecanismo de herencia múltiple en C ++ forzando la ambigüedad entre una clase de código auxiliar que solo existe con el propósito de alojar al miembro que está buscando frente a la clase que realmente está buscando para el miembro en. Esa es una pregunta interesante, sin embargo, no lo había pensado. Puede buscar otras técnicas de verificación de miembros de C ++ 11/14, he visto algunas cosas inteligentes en el nuevo estándar.
Brett Rossier

Gracias por su respuesta, creo que podría tener que verificar más en profundidad la información que da sobre la herencia, porque hasta ahora no vi ninguna correlación entre simplemente confiar en SFINAE para hacer una expresión que no sería correcta para expresar el acceso a un miembro en un parámetro de tipo de plantilla y herencia múltiple. Pero creo completamente que en C ++ incluso los conceptos distantes pueden sangrar entre sí. Ahora, para las funciones gratuitas, esta pregunta es interesante: stackoverflow.com/questions/26744589 La respuesta de TC parece usar un truco para declarar un ficticio para evitar el "identificador no declarado"
v.oddou

8

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

SFINAE para verificar las 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 coherencia del método y también funciona con tipos primitivos. (Quiero decir, 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 por defecto construible o para satisfacer ningún concepto especial. La técnica también funciona con miembros de plantilla.

Estoy esperando ansiosamente las opiniones al respecto.


7

Ahora bien, este era un buen rompecabezas pequeña - gran pregunta!

Aquí hay una alternativa a la solución de Nicola Bonelli que no depende del 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 los estándares; me gustaría recibir comentarios al respecto.


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 señalarme en la dirección correcta). El estándar C ++ 03 dice lo siguiente:

14.6.2 Nombres dependientes [temp.dep]

Apartado 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 nombres no calificados ni 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 nombres en el sitio de la llamada doToString()cuando se instancia 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 cambio 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;
}

1
No, no cumple con los estándares, aunque creo que funcionará en GCC si activa la opción -permisivo.
Leon Timmermans el

Sé que los comentarios no dan mucho espacio, pero ¿podría señalar información sobre por qué no cumple con los estándares? (No estoy discutiendo, tengo curiosidad)
Michael Burr

Mike B: el estándar dice en 3.10 p15: "Si un programa intenta acceder al valor almacenado de un objeto a través de un valor de otro que no sea uno de los siguientes tipos, el comportamiento no está definido" y esa lista de hecho no incluye el caso hacer.
Johannes Schaub - litb

44
No estoy seguro de por qué no agrega otro comentario mío: su llamada toString no está calificada. por lo que siempre llamará a la función libre y nunca a la de la base, ya que la clase base depende de un parámetro de tipo de plantilla.
Johannes Schaub - litb

@litb: Gracias por los consejos. No creo que 3.10 se aplique aquí. La llamada a toString () dentro de doToString () no está "accediendo al valor almacenado de un objeto a través de un valor l". Pero tu segundo comentario es correcto. Actualizaré la respuesta.
Michael Burr

6

La solución C ++ estándar 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, consulte:

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 del compilador si el tipo que se está probando es uno que no se puede usar como una 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 redundante () alrededor de los argumentos para deducir () en el tamaño de la expresión.


Hmm, habiendo desarrollado mi propia versión usando las ideas de publicación, descubrí que la idea tiene otros inconvenientes, así que eliminé el código de mi respuesta nuevamente. Una es que todas las funciones tienen que ser públicas en el tipo de destino. Por lo tanto, no puede verificar una función "f" en esto: struct g { void f(); private: void f(int); };porque una de las funciones es privada (esto es porque el código lo hace using g::f;, lo que hace que falle si no hay ninguna faccesible).
Johannes Schaub - litb

6

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

Actualización: la fuente se puede encontrar aquí


6

Un ejemplo usando SFINAE y la especialización parcial de 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");

5

Modifiqué la solución proporcionada en https://stackoverflow.com/a/264088/2712152 para que sea un poco más general. Además, dado que 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 deberían permitir que C99 use esto ya que usa macros variables.

La siguiente macro se puede utilizar para verificar si una clase particular tiene un tipo de definición particular o no.

/** 
 * @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 particular tiene una función miembro 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 mejorar esto para admitir funciones miembro con argumentos de plantilla. Cambie la plantilla <typename T> a template <typename T, typename ... Args>, luego puede usar "Args ..." en su macro elipsis para crear una estructura de verificación con argumentos de plantilla variadic. p.ej. Detecta el método "void onNext (const T &)" HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... ); ...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
ACyclic

4

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 sea una clase. Parece que la ambigüedad en la búsqueda de foo es un fracaso de sustitución. Lo hice funcionar en gcc, aunque no estoy seguro de si es estándar.


3

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 comprueba si hay 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


¿Hay alguna forma de incorporar la has_foollamada a la plantilla de is_supported. Lo que me gustaría es llamar algo así como: std::cout << is_supported<magic.foo(), struct1>::value << std::endl;. La razón de esto es que quiero definir una has_foopara cada firma de función diferente que quiero verificar antes de poder verificar la función.
CJCombrink

2

¿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 { };

Falla si toStringestá sobrecargado, ya que &U::toStringes ambiguo.
Yakk - Adam Nevraumont

@Yakk Creo que un elenco puede solucionar este problema.
user1095108

2

Aquí hay muchas respuestas, pero no pude encontrar una versión que realice un pedido de resolución de método real , sin usar ninguna de las funciones más nuevas de c ++ (solo usando las funciones de c ++ 98).
Nota: Esta versión está probada y funciona con vc ++ 2013, g ++ 5.2.0 y el compilador onlline.

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 extendida y solución alternativa de vc ++ 2010): http://cpp.sh/5b2vs

No hay fuente, ya que se me ocurrió.

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


No, pero lo declaro yo mismo y no usa rvalue (mira la parte superior de mi código). O simplemente podría convencerse y probar la demostración en vivo en modo c ++ 98. PD: static_assert tampoco es c ++ 98, pero hay soluciones alternativas (demostración en vivo)
user3296587

d'oh! se lo perdí. :-)
Ian Ni-Lewis

Sus afirmaciones estáticas no funcionan. Debe usar el tamaño de matriz -1 en lugar de 0 (intente poner static_assert(false);). Estaba usando esto en conexión con CRTP donde quiero determinar si la clase derivada tiene una función particular, lo que resulta que no funciona, pero sus afirmaciones siempre pasaron. Perdí un poco de cabello con ese.
los cerdos

Supongo que estás usando g ++. Tenga en cuenta que gcc / g ++ tiene una extensión que permite una matriz de tamaño cero ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
user3296587

¿Podría reescribir esto para no sobrecargar al operador? por ejemplo, elegir otro operador? Además, evite la contaminación del espacio de nombres con algo que no sea has_awesome_member?
einpoklum

1

Aquí está mi versión que maneja todas las posibles sobrecargas de funciones miembro con arity arbitraria, incluidas las funciones miembro de plantilla, posiblemente con argumentos predeterminados. Distingue 3 escenarios mutuamente excluyentes cuando se realiza una llamada de función miembro a algún tipo de clase, con tipos de argumentos 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 portarlo fácilmente (con pequeños ajustes) a no-c ++ 11 que tiene extensiones typeof (por ejemplo, gcc). Puede reemplazar la macro HAS_MEM con la suya propia.

#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


1

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éricos, deberá 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()
);

1
¿Qué tan fácil es escribir esto para no tener que depender de fitninguna biblioteca que no sea la estándar?
einpoklum

1

Con C ++ 20 puede escribir lo siguiente:

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}

0

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 un intargumento adicional que tiene prioridad sobre la función que toma longcuando se llama con 0.

Puede usar el mismo principio para las funciones que se devuelve truesi se implementa la función.

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
}

0

Tuve un problema similar:

Una clase de plantilla que puede derivarse de pocas clases base, algunas que tienen un miembro determinado y otras que no.

Lo resolví de manera similar a la respuesta "typeof" (Nicola Bonelli), pero con decltype, por lo que se compila y se ejecuta correctamente en MSVS:

#include <iostream>
#include <string>

struct Generic {};    
struct HasMember 
{
  HasMember() : _a(1) {};
  int _a;
};    

// SFINAE test
template <typename T>
class S : public T
{
public:
  std::string foo (std::string b)
  {
    return foo2<T>(b,0);
  }

protected:
  template <typename T> std::string foo2 (std::string b, decltype (T::_a))
  {
    return b + std::to_string(T::_a);
  }
  template <typename T> std::string foo2 (std::string b, ...)
  {
    return b + "No";
  }
};

int main(int argc, char *argv[])
{
  S<HasMember> d1;
  S<Generic> d2;

  std::cout << d1.foo("HasMember: ") << std::endl;
  std::cout << d2.foo("Generic: ") << std::endl;
  return 0;
}

0

Una forma más de hacerlo en C ++ 17 (inspirado en boost: hana).

Escríbelo una vez y úsalo muchas veces. No requiere has_something<T>clases de rasgos de tipo.

#include <type_traits>

template<typename T, typename F>
constexpr auto is_valid(F&& f) -> decltype(f(std::declval<T>()), true) { return true; }

template<typename>
constexpr bool is_valid(...) { return false; }

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

Ejemplo

#include <iostream>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from toString()!"; }
};

struct Example2 {
    int X;
};

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

int main() {
    static_assert(IS_VALID(Example, Foo));
    static_assert(IS_VALID(Example, Bar()));
    static_assert(!IS_VALID(Example, ZFoo));
    static_assert(!IS_VALID(Example, ZBar()));

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

-1
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}

66
"No necesitamos descripciones de respuestas" ... agregue alguna descripción informativa a su respuesta para mejorarla. Gracias.
YesThatIsMyName
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.