¿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 T
ha 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".
Respuestas:
Sí, con SFINAE puede verificar si una clase determinada proporciona un método determinado. Aquí está el código de trabajo:
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.
fuente
typeof
pordecltype
al usar C ++ 0x , por ejemplo, a través de -std = C ++ 0x.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:
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 dentrodecltype
no es válida (también conocida, la función no existe).El
void()
se utiliza para hacer el tipo de retorno de todas esas funcionesvoid
.El
0
argumento se usa para preferir laos << obj
sobrecarga si ambos están disponibles (literal0
es de tipoint
y, 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.
Ejemplo en vivo.
Y a las explicaciones. Primero,
sfinae_true
es un tipo auxiliar, y básicamente equivale a escribirdecltype(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 cualquierastd::true_type
ostd::false_type
al final, dependiendo de si eldecltype
registrotest_stream
falla o no.Por último,
std::declval
le 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 comodecltype
,sizeof
entre otros.Tenga en cuenta que
decltype
no es necesariamente necesario, ya quesizeof
(y todos los contextos no evaluados) obtuvieron esa mejora. Es solo quedecltype
ya ofrece un tipo y, como tal, es más limpio. Aquí hay unasizeof
versión de una de las sobrecargas:Los parámetros
int
ylong
todavía están allí por la misma razón. El puntero de matriz se usa para proporcionar un contexto dondesizeof
se puede usar.fuente
decltype
oversizeof
tambié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).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.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):
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:
Pero tenga en cuenta que no puede simplemente llamar a esa
toString
funció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):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 tipo de puntero de función miembro entonces!).fuente
type_check
se 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 queSign
podría llamarse un método con firma ? (Por ejemplo, siSign
=std::string(T::*)()
, permitirstd::string T::toString(int default = 42, ...)
que coincida.)T
no 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 elis_class
cheque de aumentar.toString
es una función con plantilla?C ++ 20 -
requires
expresionesCon C ++ 20 vienen conceptos y herramientas variadas como
requires
expresiones que son una forma integrada de verificar la existencia de una función. Con ellos, podría reescribir suoptionalToString
función de la siguiente manera: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
requires
expresiones desde entonces, pero aún resuelve el problema de una manera elegante. El kit de herramientas presenta algunas metafunciones, incluidas lasstd::is_detected
que 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: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
: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:
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:Luego, puede usar el
bool
para crear un cheque SFINAE.Explicación
La macro
BOOST_TTI_HAS_MEMBER_FUNCTION
genera la metafunciónhas_member_function_toString
que 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 miembrovalue
contienetrue
si la claseT
tiene una función miembrostd::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 reemplazarhas_member_function_toString<T, std::string>::value
porhas_member_function_toString<std::string T::* ()>::value
.fuente
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
typeof
extensión: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 :)
fuente
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.Una solución simple para C ++ 11:
Actualización, 3 años después: (y esto no se ha probado). Para probar la existencia, creo que esto funcionará:
fuente
template<typename>
antes de la sobrecarga variable: no se estaba considerando para su resolución.Para eso están los rasgos de tipo. Desafortunadamente, tienen que definirse manualmente. En su caso, imagine lo siguiente:
fuente
&T::x
o implícitamente mediante la unión a una referencia).type traits
para la compilación condicional en C ++ 11Bueno, 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.
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:
El siguiente ejemplo, tomado de N4502 , muestra una sonda más elaborada:
En comparación con las otras implementaciones descritas anteriormente, esta es bastante simple: un conjunto reducido de herramientas (
void_t
ydetect
) 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_t
que no funcionara como se esperaba. Yakk ya proporcionó la solución: use la siguiente definición devoid_t
( void_t en la lista de parámetros funciona pero no como tipo de retorno ):fuente
Esta es una solución de C ++ 11 para el problema general si "Si hiciera X, ¿se compilaría?"
Rasgo
has_to_string
de tal manera quehas_to_string<T>::value
estrue
si y sólo siT
tiene un método.toString
que se puede invocar con 0 argumentos en este contexto.A continuación, usaría el envío de etiquetas:
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:
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 tipoT
. Así: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:
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.
fuente
has_to_string
embargo , no importará dónde invoque .Aquí hay algunos fragmentos de uso: * Las agallas para todo esto están más abajo
Verifica si hay un miembro
x
en una clase determinada. Podría ser var, func, class, union o enum:Verifique la función del miembro
void x()
:Verifique la variable miembro
x
:Verifique la clase de miembro
x
:Verifique la unión de miembros
x
:Verifique la enumeración de miembros
x
:Verifique cualquier función de miembro
x
independientemente de la firma:O
Detalles y núcleo:
Macros (El Diablo!):
CREATE_MEMBER_CHECK:
CREATE_MEMBER_VAR_CHECK:
CREATE_MEMBER_FUNC_SIG_CHECK:
CREATE_MEMBER_CLASS_CHECK:
CREATE_MEMBER_UNION_CHECK:
CREATE_MEMBER_ENUM_CHECK:
CREATE_MEMBER_FUNC_CHECK:
CREATE_MEMBER_CHECKS:
fuente
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?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
Tenga en cuenta que incluso comprueba la coherencia del método y también funciona con tipos primitivos. (Quiero decir,
has_const_begin<int>::value
es falso y no causa un error en tiempo de compilación).Ejemplo 2
Ahora estamos buscando la firma:
void foo(MyClass&, unsigned)
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.
fuente
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
typeof
operador 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:
Entonces, parece que cuando MSVC o Comeau consideran la
toString()
función miembro deT
realizar una búsqueda de nombres en el sitio de la llamadadoToString()
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 ...
fuente
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.
fuente
struct g { void f(); private: void f(int); };
porque una de las funciones es privada (esto es porque el código lo haceusing g::f;
, lo que hace que falle si no hay ningunaf
accesible).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í
fuente
Un ejemplo usando SFINAE y la especialización parcial de plantilla, escribiendo una
Has_foo
verificación de concepto:fuente
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.
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.
Podemos usar las 2 macros anteriores para realizar las comprobaciones de has_typedef y has_mem_func como:
fuente
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" ); };
Extraño, nadie sugirió el siguiente buen truco que vi una vez en este mismo sitio:
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.
fuente
La plantilla genérica que se puede usar para verificar si alguna "característica" es compatible con el tipo:
La plantilla que comprueba si hay un método
foo
que sea compatible con la firma.double(const char*)
Ejemplos
http://coliru.stacked-crooked.com/a/83c6a631ed42cea4
fuente
has_foo
llamada a la plantilla deis_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 unahas_foo
para cada firma de función diferente que quiero verificar antes de poder verificar la función.¿Qué tal esta solución?
fuente
toString
está sobrecargado, ya que&U::toString
es ambiguo.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 ():
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'.
fuente
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.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:
Ahora puedes usarlo así:
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.
fuente
Puede omitir toda la metaprogramación en C ++ 14 y simplemente escribir esto usando
fit::conditional
la biblioteca Fit :También puede crear la función directamente desde las lambdas:
Sin embargo, si está utilizando un compilador que no admite lambdas genéricos, deberá escribir objetos de función separados:
fuente
fit
ninguna biblioteca que no sea la estándar?Con C ++ 20 puede escribir lo siguiente:
fuente
Aquí hay un ejemplo del código de trabajo.
toStringFn<T>* = nullptr
habilitará la función que toma unint
argumento adicional que tiene prioridad sobre la función que tomalong
cuando se llama con0
.Puede usar el mismo principio para las funciones que se devuelve
true
si se implementa la función.fuente
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:
fuente
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.Ejemplo
fuente
fuente