Estoy pidiendo un truco de plantilla para detectar si una clase tiene una función miembro específica de una firma determinada.
El problema es similar al citado aquí http://www.gotw.ca/gotw/071.htm pero no el mismo: en el artículo del libro de Sutter respondió a la pregunta de que una clase C DEBE PROPORCIONAR una función miembro con una firma particular, de lo contrario el programa no compilará. En mi problema, necesito hacer algo si una clase tiene esa función, de lo contrario hacer "otra cosa".
El problema :: serialización se enfrentó a un problema similar, pero no me gusta la solución que adoptaron: una función de plantilla que invoca por defecto una función libre (que debe definir) con una firma particular a menos que defina una función miembro particular ( en su caso "serializar" que toma 2 parámetros de un tipo dado) con una firma particular, de lo contrario se producirá un error de compilación. Esto es para implementar la serialización intrusiva y no intrusiva.
No me gusta esa solución por dos razones:
- Para no ser intrusivo, debe anular la función global "serializar" que se encuentra en boost :: serialization namespace, por lo que tiene EN SU CÓDIGO DE CLIENTE para abrir el refuerzo de espacio de nombres y la serialización de espacios de nombres.
- La pila para resolver ese desastre fue de 10 a 12 invocaciones de funciones.
Necesito definir un comportamiento personalizado para las clases que no tienen esa función miembro, y mis entidades están dentro de diferentes espacios de nombres (y no quiero anular una función global definida en un espacio de nombres mientras estoy en otro)
¿Me puede dar una pista para resolver este rompecabezas?
Respuestas:
No estoy seguro si te entiendo correctamente, pero puedes explotar SFINAE para detectar la presencia de funciones en tiempo de compilación. Ejemplo de mi código (prueba si la clase tiene una función miembro size_t used_memory () const).
fuente
size_t(std::vector::*p)() = &std::vector::size;
.Aquí hay una posible implementación basada en las características de C ++ 11. Detecta correctamente la función incluso si se hereda (a diferencia de la solución en la respuesta aceptada, como Mike Kinghan observa en su respuesta ).
La función que prueba este fragmento se llama
serialize
:Uso:
fuente
serialize
mismo acepta una plantilla. ¿Hay alguna forma de comprobar laserialize
existencia sin escribir el tipo exacto?La respuesta aceptada a esta pregunta de la introspección de la función miembro de compiletime, aunque es bastante popular, tiene un inconveniente que se puede observar en el siguiente programa:
Construido con GCC 4.6.3, los productos del programa
110
- para informarnos queT = std::shared_ptr<int>
hace no proporcionanint & T::operator*() const
.Si aún no es sabio con este problema, una mirada a la definición de
std::shared_ptr<T>
en el encabezado<memory>
arrojará luz. En esa implementación,std::shared_ptr<T>
se deriva de una clase base de la que heredaoperator*() const
. Por lo tanto, la creación de instancias de plantillaSFINAE<U, &U::operator*>
que constituye "encontrar" el operador paraU = std::shared_ptr<T>
no ocurrirá, porquestd::shared_ptr<T>
no tieneoperator*()
por derecho propio y la creación de instancias de plantilla no "hace herencia".Este inconveniente no afecta el conocido enfoque SFINAE, que utiliza "El truco sizeof ()", para detectar simplemente si
T
tiene alguna función miembromf
(ver, por ejemplo, esta respuesta y comentarios). Pero establecer queT::mf
existe a menudo (¿generalmente?) No es lo suficientemente bueno: es posible que también deba establecer que tiene la firma deseada. Ahí es donde puntúa la técnica ilustrada. La variante punteada de la firma deseada se inscribe en un parámetro de un tipo de plantilla que debe ser satisfecho&T::mf
para que la sonda SFINAE tenga éxito. Pero esta técnica de creación de instancias de plantilla da la respuesta incorrecta cuandoT::mf
se hereda.Una técnica segura de SFINAE para la introspección en tiempo de compilación
T::mf
debe evitar el uso&T::mf
dentro de un argumento de plantilla para crear una instancia de un tipo del que depende la resolución de la plantilla de función SFINAE. En cambio, la resolución de la función de plantilla SFINAE puede depender solo de las declaraciones de tipo exactamente pertinentes utilizadas como tipos de argumento de la función de sonda SFINAE sobrecargada.A modo de respuesta a la pregunta que cumple con esta restricción, ilustraré la detección en tiempo de compilación de
E T::operator*() const
, para arbitrariaT
yE
. El mismo patrón se aplicará mutatis mutandis para buscar cualquier otra firma de método miembro.En esta solución, la función de sonda SFINAE sobrecargada
test()
se "invoca de forma recursiva". (Por supuesto, en realidad no se invoca en absoluto; simplemente tiene los tipos de retorno de invocaciones hipotéticas resueltas por el compilador).Necesitamos investigar al menos uno y como máximo dos puntos de información:
T::operator*()
en absoluto? Si no, hemos terminado.T::operator*()
existe, ¿es su firmaE T::operator*() const
?Obtenemos las respuestas evaluando el tipo de retorno de una sola llamada a
test(0,0)
. Eso lo hace:Esta llamada puede resolverse a la
/* SFINAE operator-exists :) */
sobrecarga detest()
, o puede resolverse a la/* SFINAE game over :( */
sobrecarga. No se puede resolver la/* SFINAE operator-has-correct-sig :) */
sobrecarga, porque uno espera solo un argumento y estamos pasando dos.¿Por qué estamos pasando dos? Simplemente para forzar la resolución a excluir
/* SFINAE operator-has-correct-sig :) */
. El segundo argumento no tiene otro significado.Esta llamada a
test(0,0)
se resolverá/* SFINAE operator-exists :) */
en caso de que el primer argumento 0 satifique el primer tipo de parámetro de esa sobrecarga, que esdecltype(&A::operator*)
, conA = T
. 0 satisfará ese tipo en caso de queT::operator*
exista.Supongamos que el compilador dice Sí a eso. Luego continúa
/* SFINAE operator-exists :) */
y necesita determinar el tipo de retorno de la llamada de función, que en ese caso esdecltype(test(&A::operator*))
: el tipo de retorno de otra llamada mástest()
.Esta vez, estamos pasando un solo argumento,
&A::operator*
que ahora sabemos que existe, o no estaríamos aquí. Una llamada atest(&A::operator*)
podría resolverse ao/* SFINAE operator-has-correct-sig :) */
nuevamente o podría resolverse a/* SFINAE game over :( */
. La llamada coincidirá/* SFINAE operator-has-correct-sig :) */
en caso de que&A::operator*
satisfaga el tipo de parámetro único de esa sobrecarga, que esE (A::*)() const
, conA = T
.El compilador dirá Sí aquí si
T::operator*
tiene esa firma deseada, y luego nuevamente tendrá que evaluar el tipo de retorno de la sobrecarga. No más "recurrencias" ahora: lo esstd::true_type
.Si el compilador no elige
/* SFINAE operator-exists :) */
para la llamadatest(0,0)
o no elige/* SFINAE operator-has-correct-sig :) */
para la llamadatest(&A::operator*)
, entonces en cualquier caso va con/* SFINAE game over :( */
y el tipo de retorno final esstd::false_type
.Aquí hay un programa de prueba que muestra la plantilla que produce las respuestas esperadas en una muestra variada de casos (GCC 4.6.3 nuevamente).
¿Hay nuevos defectos en esta idea? ¿Se puede hacer más genérico sin volver a caer en el obstáculo que evita?
fuente
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
Esto debería ser suficiente si conoce el nombre de la función miembro que espera. (En este caso, la función bla no se puede crear una instancia si no hay una función miembro (escribir una que funcione de todos modos es difícil porque hay una falta de especialización parcial de la función. Es posible que necesite usar plantillas de clase) Además, la estructura de habilitación (que es similar a enable_if) también se podría incluir en el tipo de función que desea que tenga como miembro.
fuente
Aquí hay una versión más simple de la respuesta de Mike Kinghan. Esto detectará métodos heredados. También verificará la firma exacta (a diferencia del enfoque de jrok que permite conversiones de argumentos).
Ejemplo ejecutable
fuente
using
traer sobrecargas de la clase base. A mí me funciona en MSVC 2015 y con Clang-CL. Sin embargo, no funciona con MSVC 2012.Puede usar std :: is_member_function_pointer
fuente
&A::foo
habrá un error de compilación si no hay nadafoo
en absolutoA
? Leí la pregunta original porque se supone que funciona con cualquier clase de entrada, no solo aquellas que tienen algún tipo de miembro nombradofoo
.Llegué con el mismo tipo de problema, y encontré las soluciones propuestas aquí muy interesantes ... pero tenía el requisito de una solución que:
Encontré otro hilo proponiendo algo como esto, basado en una discusión de BOOST . Aquí está la generalización de la solución propuesta como declaración de dos macros para la clase de rasgos, siguiendo el modelo de las clases boost :: has_ * .
Estas macros se expanden a una clase de rasgos con el siguiente prototipo:
Entonces, ¿cuál es el uso típico que se puede hacer con esto?
fuente
Para lograr esto, necesitaremos usar:
type_traits
encabezado, querremos devolvertrue_type
aofalse_type
de nuestras sobrecargastrue_type
sobrecarga que espera unaint
y lafalse_type
sobrecarga que espera que los Parámetros Variados exploten: "La prioridad más baja de la conversión de puntos suspensivos en la resolución de sobrecarga"true_type
función que usaremosdeclval
ydecltype
nos permitirá detectar la función independientemente de las diferencias de tipo de retorno o sobrecargas entre métodosPuedes ver un ejemplo en vivo de esto aquí . Pero también lo explicaré a continuación:
Quiero verificar la existencia de una función llamada
test
que toma un tipo convertibleint
, entonces necesitaría declarar estas dos funciones:decltype(hasTest<a>(0))::value
estrue
(Tenga en cuenta que no es necesario crear una funcionalidad especial para hacer frente a lavoid a::test()
sobrecarga,void a::test(int)
se acepta)decltype(hasTest<b>(0))::value
estrue
(porqueint
es convertible adouble
int b::test(double)
es aceptado, independiente del tipo de retorno)decltype(hasTest<c>(0))::value
esfalse
(c
no tiene un método llamadotest
que acepte un tipo convertible a partir deint
ahí, por lo que esto no se acepta)Esta solución tiene 2 inconvenientes:
test()
método?Por lo tanto, es importante que estas funciones se declaren en un espacio de nombres de detalles, o idealmente si solo se usan con una clase, esa clase debería declararlas de forma privada. Con ese fin, he escrito una macro para ayudarlo a resumir esta información:
Podrías usar esto como:
Posteriormente llamando
details::test_int<a>::value
odetails::test_void<a>::value
cederíatrue
ofalse
con el propósito de código en línea o meta-programación.fuente
Para no ser intrusivo, también puede poner
serialize
en el espacio de nombres de la clase que se está serializando, o de la clase de archivo, gracias a la búsqueda de Koenig . Consulte Espacios de nombres para anulaciones de funciones gratuitas para obtener más detalles. :-)Abrir cualquier espacio de nombres dado para implementar una función gratuita es simplemente incorrecto. (por ejemplo, se supone que no debe abrir el espacio
std
de nombres para implementarswap
sus propios tipos, sino que debe usar la búsqueda de Koenig en su lugar).fuente
Parece que quieres el idioma del detector. Las respuestas anteriores son variaciones de esto que funcionan con C ++ 11 o C ++ 14.
La
std::experimental
biblioteca tiene características que hacen esencialmente esto. Reelaborando un ejemplo de arriba, podría ser:Si no puede usar std :: experimental, se puede hacer una versión rudimentaria como esta:
Como has_serialize_t es realmente std :: true_type o std :: false_type, se puede usar a través de cualquiera de los modismos comunes de SFINAE:
O mediante el envío con resolución de sobrecarga:
fuente
Bueno. Segundo intento. Está bien si tampoco te gusta este, estoy buscando más ideas.
El artículo de Herb Sutter habla sobre rasgos. Por lo tanto, puede tener una clase de rasgos cuya instanciación predeterminada tenga el comportamiento alternativo, y para cada clase donde exista su función miembro, entonces la clase de rasgos está especializada para invocar la función miembro. Creo que el artículo de Herb menciona una técnica para hacer esto para que no implique muchas copias y pegados.
Sin embargo, como dije, tal vez no desee el trabajo adicional involucrado con las clases de "etiquetado" que implementan ese miembro. En cuyo caso, estoy buscando una tercera solución ...
fuente
Sin el soporte de C ++ 11 (
decltype
) esto podría funcionar:SSCCE
¿Cómo funciona?
A
,Aa
yB
son las clases en cuestión,Aa
siendo la especial que hereda el miembro que estamos buscando.En el
FooFinder
latrue_type
yfalse_type
son los reemplazos para el corresponsal C ++ 11 clases. También para la comprensión de la meta programación de plantillas, revelan la base misma del truco del tamaño de SFINAE.El
TypeSink
es una estructura de plantilla que se utiliza más tarde para hundir el resultado integral delsizeof
operador en una instanciación de plantilla para formar un tipo.La
match
función es otro tipo de plantilla SFINAE que se deja sin una contraparte genérica. Por lo tanto, solo se puede crear una instancia si el tipo de su argumento coincide con el tipo para el que estaba especializado.Ambas
test
funciones junto con la declaración enum finalmente forman el patrón central SFINAE. Hay uno genérico que usa puntos suspensivos que devuelve elfalse_type
y una contraparte con argumentos más específicos para tener prioridad.Para poder instanciar la
test
función con un argumento de plantillaT
, lamatch
función debe ser instanciada, ya que se requiere su tipo de retorno para instanciar elTypeSink
argumento. La advertencia es que&U::foo
, al estar envuelto en un argumento de función, no se hace referencia desde dentro de una especialización de argumento de plantilla, por lo que la búsqueda de miembros heredados todavía tiene lugar.fuente
Si está utilizando la locura de Facebook, su macro lista para usar le ayudará a:
Aunque los detalles de implementación son los mismos que en la respuesta anterior, usar una biblioteca es más simple.
fuente
Tenía una necesidad similar y me encontré con este SO. Aquí se proponen muchas soluciones interesantes / poderosas, aunque es un poco largo para una necesidad específica: detectar si una clase tiene una función miembro con una firma precisa. Así que leí / probé y obtuve mi versión que podría ser de interés. Detecta:
Con una firma precisa. Como no necesito capturar ninguna firma (eso requeriría una solución más complicada), esta es una opción para mí. Básicamente utilizaba enable_if_t .
Salida:
fuente
Sobre la base de JROK 's respuesta , he evitado el uso de clases y / o funciones de plantilla anidadas.
Podemos usar las macros anteriores de la siguiente manera:
Las sugerencias son bienvenidas.
fuente