c++ - tube - tipos de template
Plantillas C++ que aceptan solo ciertos tipos (13)
En Java puede definir una clase genérica que acepte solo los tipos que amplíen la clase de su elección, por ejemplo:
public class ObservableList<T extends List> {
...
}
Esto se hace usando la palabra clave "extends".
¿Hay algún equivalente simple a esta palabra clave en C ++?
¿Hay algún equivalente simple a esta palabra clave en C ++?
No.
Dependiendo de lo que intentes lograr, puede haber sustitutos adecuados (o incluso mejores).
Revisé un código STL (en Linux, creo que es el derivado de la implementación de SGI). Tiene "aserciones conceptuales"; por ejemplo, si necesita un tipo que comprenda *x
y ++x
, la afirmación del concepto contendría ese código en una función de no hacer nada (o algo similar). Requiere una sobrecarga, por lo que podría ser inteligente colocarlo en una macro cuya definición dependa de #ifdef debug
.
Si la relación de la subclase es realmente lo que desea saber, puede afirmar en el constructor esa T instanceof list
(excepto que se deletrea de manera diferente en C ++). De esta forma, puedes probar tu salida del compilador sin poder verificarlo por ti.
Bueno, podrías crear tu plantilla leyendo algo como esto:
template<typename T>
class ObservableList {
std::list<T> contained_data;
};
Sin embargo, esto hará que la restricción sea implícita, y además no podrá suministrar nada que se parezca a una lista. Hay otras formas de restringir los tipos de contenedores 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.
A mi leal saber y entender, 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 escribe mediante el uso de typedefs específicos dentro de su plantilla. Esto asegurará que la compilación de la especialización de la plantilla para un tipo que no incluye esa typedef particular fallará, por lo que puede admitir selectivamente / no admitir ciertos tipos.
En C ++ 11, la introducción de conceptos debería facilitar esto, pero no creo que haga exactamente lo que usted desearía.
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 borrado de tipo , que es una técnica dinámica , en lugar de polimorfismo de tiempo de compilación , que es una técnica estática . Debería ser obvio por qué estas dos tácticas muy diferentes no se gelifican bien.
En lugar de intentar usar una construcción en tiempo de compilación para simular un tiempo de ejecución, veamos lo que realmente hace la extends
: según y Wikipedia , extends se usa para indicar subclases.
C ++ también es compatible con la creación de subclases.
También muestra una clase de contenedor, que utiliza borrado de tipo genérico y se extiende para realizar una comprobación de tipo. En C ++, tiene que hacer usted mismo el tipo de maquinaria de borrado, que es simple: hacer 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, et voila:
typedef std::list<superclass*> subclasses_of_superclass_only_list;
Por ejemplo:
class Shape { };
class Triangle : public Shape { };
typedef std::list<Shape*> only_shapes_list;
only_shapes_list shapes;
shapes.push_back(new Triangle()); // Works, triangle is kind of shape
shapes.push_back(new int(30)); // Error, int''s are not shapes
Ahora, parece que List es una interfaz, que representa un tipo 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, puede implementar fácilmente su ejemplo de Java en C ++, sin Conceptos o especializaciones de plantillas. También funcionaría tan lento como los genéricos de estilo Java debido a las búsquedas de tablas virtuales, pero a menudo puede ser una pérdida aceptable.
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 verificación de conceptos, por ejemplo, usando BCCL de Boost .
Esto normalmente no está justificado en C ++, como otras respuestas aquí han notado. En C ++, tendemos a definir tipos genéricos basados en otras restricciones además de "heredar de esta clase". Si realmente quería hacer eso, es bastante fácil de hacer en C ++ 11 y <type_traits>
:
#include <type_traits>
template<typename T>
class observable_list {
static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
// code here..
};
Esto rompe muchos de los conceptos que las personas esperan en C ++. Es mejor usar trucos como definir tus propios rasgos. Por ejemplo, quizás observable_list
desea aceptar cualquier tipo de contenedor que tenga el tipo const_iterator
y una función de miembro de begin
y end
que devuelva const_iterator
. Si restringe esto a las clases que heredan de la list
, un usuario que tenga su propio tipo que no herede de la list
pero proporcione estas funciones miembro y typedefs no podrá usar su lista observable_list
.
Hay dos soluciones para este problema, una de ellas es no restringir nada y confiar en el tipado 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 interfaz. La gran estafa de esta solución es que implica una escritura extra que puede verse como algo molesto. Sin embargo, el lado positivo es que podrá escribir sus propios mensajes de error a la static_assert
.
Para completar, se proporciona la solución al ejemplo anterior:
#include <type_traits>
template<typename...>
struct void_ {
using type = void;
};
template<typename... Args>
using Void = typename void_<Args...>::type;
template<typename T, typename = void>
struct has_const_iterator : std::false_type {};
template<typename T>
struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};
struct has_begin_end_impl {
template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
typename End = decltype(std::declval<const T&>().end())>
static std::true_type test(int);
template<typename...>
static std::false_type test(...);
};
template<typename T>
struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};
template<typename T>
class observable_list {
static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
// code here...
};
Hay muchos conceptos que se muestran en el ejemplo anterior 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.
La solución simple, que nadie ha mencionado aún, es simplemente ignorar el problema. Si trato de usar un int
como tipo de plantilla en una plantilla de función que espera una clase de contenedor como vector o list, entonces 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 obtienes van a ser 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 obtener mensajes de error más agradables (o si desea detectar casos que no producirían un error de compilación, pero aún así no tienen sentido), puede usar la afirmación estática de Boost o bien, dependiendo de qué tan complejo desee hacerlo. la biblioteca Boost concept_check.
Con un compilador actualizado, tiene incorporado static_assert
, que podría utilizarse en su lugar.
No existe una palabra clave para tales comprobaciones de tipo, pero puede poner algún código que al menos fallará de forma ordenada:
(1) Si desea que una plantilla de función acepte solo parámetros de una cierta clase base X, asígnela a una referencia X en su función. (2) Si desea aceptar funciones pero no primitivas o viceversa, o si desea filtrar las clases de otras maneras, llame a una función auxiliar (vacía) de la plantilla 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 estas comprobaciones de tipo en toda la clase.
Probablemente puedas ponerlo en alguna macro inteligente para aliviar tu dolor. :)
Podemos usar std::is_base_of
y std::enable_if
:
( static_assert
se puede eliminar, las clases anteriores se pueden implementar de forma personalizada o usar desde boost si no podemos hacer referencia a type_traits
)
#include <type_traits>
#include <list>
class Base {};
class Derived: public Base {};
#if 0 // wrapper
template <class T> class MyClass /* where T:Base */ {
private:
static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
typename std::enable_if<std::is_base_of<Base, T>::value, T>::type inner;
};
#elif 0 // base class
template <class T> class MyClass: /* where T:Base */
protected std::enable_if<std::is_base_of<Base, T>::value, T>::type {
private:
static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
};
#elif 1 // list-of
template <class T> class MyClass /* where T:list<Base> */ {
static_assert(std::is_base_of<Base, typename T::value_type>::value , "T::value_type is not derived from Base");
typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type base;
typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type::value_type value_type;
};
#endif
int main() {
#if 0 // wrapper or base-class
MyClass<Derived> derived;
MyClass<Base> base;
// error:
MyClass<int> wrong;
#elif 1 // list-of
MyClass<std::list<Derived>> derived;
MyClass<std::list<Base>> base;
// error:
MyClass<std::list<int>> wrong;
#endif
// all of the static_asserts if not commented out
// or "error: no type named ‘type’ in ‘struct std::enable_if<false, ...>’ pointing to:
// 1. inner
// 2. MyClass
// 3. base + value_type
}
Por lo que 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 proporciona 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 comenzaron 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.
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 no deberías 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 ahí porque Java escribe borrado en genéricos, por lo que Java no puede encontrar la manera de llamar a los métodos en función del valor de los parámetros de tipo solo.
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 a "Duck Typing" de Python, pero se hace en tiempo de compilación.
Un simple ejemplo que muestra el poder de las plantillas:
// Sum a vector of some type.
// Example:
// int total = sum({1,2,3,4,5});
template <typename T>
T sum(const vector<T>& vec) {
T total = T();
for (const T& x : vec) {
total += x;
}
return total;
}
Esta función suma puede sumar un vector de cualquier tipo que soporte las operaciones correctas. Funciona con las dos primitivas, como int / long / float / double, y los 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 boxeo / unboxing 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 explícitas de tipo, todavía son seguras para los tipos y no compilarán con código que no admita las operaciones correctas.
Sugiero usar la función de is_base_of
estática de Boost en concierto con is_base_of
de la biblioteca Boost Type Traits:
template<typename T>
class ObservableList {
BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
...
};
En algunos casos más simples, puede simplemente reenviar-declarar una plantilla global, pero solo definir (explícita o parcialmente especializarla) para los tipos válidos:
template<typename T> class my_template; // Declare, but don''t define
// int is a valid type
template<> class my_template<int> {
...
};
// All pointer types are valid
template<typename T> class my_template<T*> {
...
};
// All other types are invalid, and will cause linker error messages.
[Minor EDIT 12/12/2013: el uso de una plantilla declarada pero no definida dará como resultado mensajes de error del vinculador , no del compilador.]
Un equivalente que solo acepta los tipos T derivados del tipo List se parece a
template<typename T,
typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr>
class ObservableList
{
// ...
};
class Base
{
struct FooSecurity{};
};
template<class Type>
class Foo
{
typename Type::FooSecurity If_You_Are_Reading_This_You_Tried_To_Create_An_Instance_Of_Foo_For_An_Invalid_Type;
};
Asegúrese de que las clases derivadas hereden la estructura de FooSecurity y el compilador se molestará en todos los lugares correctos.