En Java, puede definir una clase genérica que acepte solo los tipos que amplían la clase de su elección, por ejemplo:
public class ObservableList<T extends List> {
...
}
Esto se hace usando la palabra clave "extend".
¿Hay algún equivalente simple a esta palabra clave en C ++?
Respuestas:
Sugiero usar la función de aserción estática de Boost en concierto con
is_base_of
la biblioteca Boost Type Traits:En otros casos más simples, puede simplemente declarar hacia adelante una plantilla global, pero solo definirla (especializarse explícita o parcialmente) para los tipos válidos:
[EDIT menor 12/12/2013: el uso de una plantilla declarada pero no definida dará como resultado mensajes de error del vinculador , no del compilador.]
fuente
myBaseType
exactamente. Antes de descartar Boost, debe saber que la mayor parte es un código de plantilla de solo encabezado, por lo que no hay costos de memoria o tiempo en tiempo de ejecución para las cosas que no usa. Además, las cosas particulares que usaría aquí (BOOST_STATIC_ASSERT()
yis_base_of<>
) se pueden implementar usando solo declaraciones (es decir, sin definiciones reales de funciones o variables) para que tampoco tomen espacio ni tiempo.static_assert(std::is_base_of<List, T>::value, "T must extend list")
.my_template<int> x;
omy_template<float**> y;
verificar que el compilador lo permita, y luego declarar una variablemy_template<char> z;
y verificar que no.Esto generalmente no está justificado en C ++, como han señalado otras respuestas aquí. En C ++ tendemos a definir tipos genéricos basados en otras restricciones que no sean "hereda de esta clase". Si realmente quería hacer eso, es bastante fácil hacerlo en C ++ 11 y
<type_traits>
:Sin embargo, esto rompe muchos de los conceptos que las personas esperan en C ++. Es mejor usar trucos como definir tus propios rasgos. Por ejemplo, tal vez
observable_list
quiera aceptar cualquier tipo de envase que tiene las typedefsconst_iterator
y unabegin
yend
función miembro que los retornosconst_iterator
. Si restringe esto a clases que heredan delist
entonces, un usuario que tiene su propio tipo que no heredalist
pero que proporciona estas funciones miembro y typedefs no podrá usar suobservable_list
.Hay dos soluciones a este problema, una de ellas es no restringir nada y confiar en la tipificación de patos. Una gran desventaja de esta solución es que implica una gran cantidad de errores que pueden ser difíciles de asimilar para los usuarios. Otra solución es definir rasgos para restringir el tipo proporcionado para cumplir con los requisitos de la interfaz. El gran inconveniente de esta solución es que implica una escritura adicional que puede considerarse molesta. Sin embargo, el lado positivo es que podrá escribir sus propios mensajes de error a la
static_assert
.Para completar, se da la solución al ejemplo anterior:
En el ejemplo anterior se muestran muchos conceptos que muestran las características de C ++ 11. Algunos términos de búsqueda para curiosos son plantillas variadas, SFINAE, expresión SFINAE y rasgos de tipo.
fuente
template<class T:list>
es un concepto tan ofensivo. Gracias por el consejo.La solución simple, que nadie ha mencionado todavía, es ignorar el problema. Si intento usar un
int
tipo de plantilla en una plantilla de función que espera una clase de contenedor como vector o lista, obtendré un error de compilación. Crudo y simple, pero resuelve el problema. El compilador intentará usar el tipo que especifique, y si eso falla, genera un error de compilación.El único problema con eso es que los mensajes de error que recibas serán difíciles de leer. Sin embargo, es una forma muy común de hacer esto. La biblioteca estándar está llena de plantillas de funciones o clases que esperan cierto comportamiento del tipo de plantilla, y no hacen nada para verificar que los tipos utilizados sean válidos.
Si desea mensajes de error más agradables (o si desea detectar casos que no producirían un error del compilador, pero que aún no tienen sentido), puede, dependiendo de lo complejo que desee hacerlo, usar la afirmación estática de Boost o la biblioteca Boost concept_check.
Con un compilador actualizado, tiene un built_in
static_assert
, que podría usarse en su lugar.fuente
T
y desde dónde se llama este código? Sin algún contexto, no tengo ninguna posibilidad de entender ese fragmento de código. Pero lo que dije es cierto. Si intenta llamartoString()
a un tipo que no tiene unatoString
función miembro, obtendrá un error de compilación.Podemos usar
std::is_base_of
ystd::enable_if
:(
static_assert
se puede eliminar, las clases anteriores se pueden implementar de forma personalizada o se pueden usar desde boost si no podemos hacer referenciatype_traits
)fuente
Hasta donde sé, esto no es posible actualmente en C ++. Sin embargo, hay planes para agregar una característica llamada "conceptos" en el nuevo estándar C ++ 0x que brinde la funcionalidad que está buscando. Este artículo de Wikipedia sobre C ++ Concepts lo explicará con más detalle.
Sé que esto no soluciona su problema inmediato, pero hay algunos compiladores de C ++ que ya han comenzado a agregar características del nuevo estándar, por lo que podría ser posible encontrar un compilador que ya haya implementado la función de conceptos.
fuente
static_assert
y SFINAE, como muestran las otras respuestas. El problema restante para alguien que viene de Java o C #, o Haskell (...) es que el compilador de C ++ 20 no hace una verificación de definición contra los conceptos requeridos, lo que hacen Java y C #.Creo que todas las respuestas anteriores han perdido de vista el bosque por los árboles.
Los genéricos de Java no son lo mismo que las plantillas ; usan el tipo de borrado , que es una técnica dinámica , en lugar de compilar el polimorfismo de tiempo , que es una técnica estática . Debería ser obvio por qué estas dos tácticas muy diferentes no funcionan bien.
En lugar de intentar usar una construcción en tiempo de compilación para simular una en tiempo de ejecución, veamos lo que
extends
realmente hace: de acuerdo con Stack Overflow y Wikipedia , se usa extender para indicar subclases.C ++ también admite subclases.
También muestra una clase de contenedor, que utiliza el borrado de tipo en forma de genérico, y se extiende para realizar una verificación de tipo. En C ++, debe hacer la maquinaria de borrado de tipo usted mismo, que es simple: haga un puntero a la superclase.
Vamos a envolverlo en un typedef, para que sea más fácil de usar, en lugar de hacer una clase completa, y listo:
typedef std::list<superclass*> subclasses_of_superclass_only_list;
Por ejemplo:
Ahora, parece que List es una interfaz, que representa una especie de colección. Una interfaz en C ++ sería simplemente una clase abstracta, es decir, una clase que implementa nada más que métodos virtuales puros. Con este método, podría implementar fácilmente su ejemplo de Java en C ++, sin ningún concepto o especialización de plantilla. También funcionaría tan lento como los genéricos de estilo Java debido a las búsquedas de tablas virtuales, pero esto a menudo puede ser una pérdida aceptable.
fuente
Un equivalente que solo acepta tipos T derivados del tipo Lista se parece a
fuente
Resumen ejecutivo: no hagas eso.
La respuesta de j_random_hacker te dice cómo hacer esto. Sin embargo, también me gustaría señalar que usted debe no hacer esto. El objetivo de las plantillas es que pueden aceptar cualquier tipo compatible, y las restricciones de tipo de estilo Java lo rompen.
Las restricciones de tipo de Java son un error, no una característica. Están allí porque Java borra los tipos genéricos, por lo que Java no puede descubrir cómo llamar a los métodos basándose solo en el valor de los parámetros de tipo.
C ++ por otro lado no tiene tal restricción. Los tipos de parámetros de plantilla pueden ser de cualquier tipo compatible con las operaciones con las que se utilizan. No tiene que haber una clase base común. Esto es similar al "Duck Typing" de Python, pero hecho en tiempo de compilación.
Un ejemplo simple que muestra el poder de las plantillas:
Esta función de suma puede sumar un vector de cualquier tipo que admita las operaciones correctas. Funciona con primitivas como int / long / float / double y tipos numéricos definidos por el usuario que sobrecargan el operador + =. Diablos, incluso puedes usar esta función para unir cadenas, ya que admiten + =.
No es necesario el encajonamiento / desempaquetado de primitivas.
Tenga en cuenta que también construye nuevas instancias de T usando T (). Esto es trivial en C ++ usando interfaces implícitas, pero no es realmente posible en Java con restricciones de tipo.
Si bien las plantillas de C ++ no tienen restricciones de tipo explícitas, siguen siendo de tipo seguro y no se compilarán con código que no sea compatible con las operaciones correctas.
fuente
Eso no es posible en C ++ simple, pero puede verificar los parámetros de la plantilla en tiempo de compilación a través de la Comprobación de conceptos, por ejemplo, utilizando BCCL de Boost .
A partir de C ++ 20, los conceptos se están convirtiendo en una característica oficial del lenguaje.
fuente
Asegúrese de que las clases derivadas hereden la estructura de FooSecurity y el compilador se alterará en todos los lugares correctos.
fuente
Type::FooSecurity
se usa en la clase de plantilla. Si la clase, aprobada en el argumento de plantilla, no lo ha hechoFooSecurity
, intentar usarla causa un error. Es seguro que si la clase pasada en el argumento de plantilla no tiene FooSecurity, no se deriva de ellaBase
.Uso de concepto de C ++ 20
https://en.cppreference.com/w/cpp/language/constraints cppreference está dando el caso de uso de herencia como un ejemplo de concepto explícito:
Para múltiples bases, supongo que la sintaxis será:
Parece que GCC 10 lo implementó: https://gcc.gnu.org/gcc-10/changes.html y puede obtenerlo como PPA en Ubuntu 20.04 . https://godbolt.org/ Mi GCC 10.1 local aún no lo reconocía
concept
, así que no estoy seguro de lo que está sucediendo.fuente
No.
Dependiendo de lo que intente lograr, puede haber sustitutos adecuados (o incluso mejores).
He revisado algunos códigos STL (en Linux, creo que es el que deriva de la implementación de SGI). Tiene "aserciones conceptuales"; por ejemplo, si necesita un tipo que comprenda
*x
y++x
, la aserción del concepto contendría ese código en una función de no hacer nada (o algo similar). Requiere cierta sobrecarga, por lo que puede ser inteligente colocarlo en una macro cuya definición depende#ifdef debug
.Si la relación de subclase es realmente lo que desea saber, podría afirmar en el constructor que
T instanceof list
(excepto que está "deletreada" de manera diferente en C ++). De esa manera, puede probar su salida del compilador sin poder verificarlo por usted.fuente
No hay una palabra clave para tales comprobaciones de tipo, pero puede poner un código que al menos fallará de manera ordenada:
(1) Si desea que una plantilla de función solo acepte parámetros de una determinada clase base X, asígnela a una referencia X en su función. (2) Si desea aceptar funciones pero no primitivas o viceversa, o desea filtrar clases de otras maneras, llame a una función auxiliar de plantilla (vacía) dentro de su función que solo está definida para las clases que desea aceptar.
Puede usar (1) y (2) también en las funciones miembro de una clase para forzar estos controles de tipo en toda la clase.
Probablemente pueda ponerlo en una Macro inteligente para aliviar su dolor. :)
fuente
Bueno, podrías crear tu plantilla leyendo algo como esto:
Sin embargo, esto hará que la restricción sea implícita, además de que no puede proporcionar nada que parezca una lista. Hay otras formas de restringir los tipos de contenedor utilizados, por ejemplo, haciendo uso de tipos de iteradores específicos que no existen en todos los contenedores, pero nuevamente esto es más una restricción implícita que explícita.
Que yo sepa, una construcción que refleje la declaración de la declaración de Java en toda su extensión no existe en el estándar actual.
Hay formas de restringir los tipos que puede usar dentro de una plantilla que escriba usando typedefs específicos dentro de su plantilla. Esto asegurará que la compilación de la especialización de plantilla para un tipo que no incluye ese tipo de definición en particular fallará, por lo que puede admitir selectivamente / no admitir ciertos tipos.
En C ++ 11, la introducción de conceptos debería facilitarlo, pero tampoco creo que haga exactamente lo que desea.
fuente