Esta pregunta ya tiene respuestas aquí :
Comentarios
Respuesta
Además del void *
puntero que está cubierto en la respuesta de Robert , se utilizó una técnica como esta (descargo de responsabilidad: Memoria de 20 años):
#define WANTIMP #define TYPE int #include "collection.h" #undef TYPE #define TYPE string #include "collection.h" #undef TYPE int main() { Collection_int lstInt; Collection_string lstString; }
Donde he olvidado la magia exacta del preprocesador dentro de collection.h
, pero estaba algo como esto:
class Collection_ ## TYPE { public: Collection_ ## TYPE () {} void Add(TYPE value); private: TYPE *list; size_t n; size_t a; } #ifdef WANTIMP void Collection_ ## TYPE ::Add(TYPE value) #endif
Comentarios
Respuesta
El La forma tradicional de implementar genéricos sin tener genéricos (la razón por la que se crearon las plantillas) es usar un puntero vacío.
typedef struct Item{ void* data; } Item; typedef struct Node{ Item Item; struct Node* next; struct Node* previous; } Node;
En este código de ejemplo, un árbol binario o Se puede representar una lista doblemente enlazada. Debido a que item
encapsula un puntero vacío, cualquier tipo de datos se puede almacenar allí. Por supuesto, deberías conocer el tipo de datos en tiempo de ejecución para poder convertirlo en un objeto utilizable.
Comentarios
Respuesta
Como se señaló en otras respuestas, puede usar void*
para estructuras de datos genéricas.Para otros tipos de polimorfismo paramétrico, se usaron macros de preprocesador si algo se repetía mucho mucho (como docenas de veces). Sin embargo, para ser honesto, la mayoría de las veces para la repetición moderada, la gente simplemente copia y pega, luego cambia los tipos, porque hay muchas trampas con las macros que las hacen problemáticas.
Realmente necesitamos un nombre para la inversa de la paradoja blub , donde la gente tiene dificultades para imaginarse la programación en un lenguaje menos expresivo, porque esto aparece mucho en este sitio. Si nunca ha usado un lenguaje con formas expresivas de implementar el polimorfismo paramétrico, realmente no sabe lo que se está perdiendo. Simplemente acepta copiar y pegar como algo molesto, pero necesario.
Existen ineficiencias en los idiomas que elijas actualmente de las que ni siquiera eres consciente. En veinte años la gente se preguntará cómo los eliminó. La respuesta corta es que no lo hizo, porque no sabía que podía hacerlo.
Comentarios
Respuesta
Recuerdo cuando gcc se envió con genclass
, un programa que tomaba como entrada un conjunto de tipos de parámetros ( por ejemplo, clave y valor para un mapa) y un archivo de sintaxis especial que describía un tipo parametrizado (por ejemplo, un mapa o un vector) y generaba implementaciones de C ++ válidas con los tipos de parámetros completados.
Entonces, si necesitaba Map<int, string>
y Map<string, string>
(esta no era la sintaxis real, ten en cuenta que) tenías que ejecute ese programa dos veces para generar algo como map_string_string.hy map_int_string.hy luego utilícelos en su código.
Consulte la página de manual de para genclass
y la documentación de GNU C ++ Library 2.0 para más detalles.
Responder
[Para el OP: «No estoy tratando de molestarlo personalmente, sino de aumentar su conciencia y la de los demás» de pensar en la lógica de la pregunta ( s) preguntado en SE y en otros lugares. ¡No se lo tome personalmente!]
El título de la pregunta es bueno, pero está limitando severamente el alcance de sus respuestas al incluir «… situaciones en las que necesitaban la generación de código en tiempo de compilación. «En esta página existen muchas buenas respuestas a la pregunta sobre cómo generar código en tiempo de compilación en C ++ sin plantillas, pero para responder a la pregunta que planteó originalmente:
¿Qué hacía la gente antes de las plantillas en C ++?
La respuesta es, por supuesto, que ellos (nosotros) no los usamos. Sí, estoy siendo irónico, pero los detalles de la pregunta en el cuerpo parecen suponer (quizás exageradamente) que a todos les encantan las plantillas y que no se podría haber codificado sin ellas.
Como ejemplo, completé muchos proyectos de codificación en varios lenguajes sin necesidad de generar código en tiempo de compilación, y creo que otros también lo han hecho. Claro, el problema resuelto por las plantillas fue una picazón lo suficientemente grande como para que alguien realmente lo rascara, pero el escenario propuesto por esta pregunta era, en gran parte, inexistente.
Considere una pregunta similar en los automóviles:
¿Cómo cambiaban los conductores de una marcha a otra, utilizando un método automatizado que cambiaba de marcha por usted, antes de que se inventara la transmisión automática?
La pregunta es, por supuesto, tonta. Preguntar cómo una persona hizo X antes de que se inventara X no es realmente una pregunta válida. La respuesta es generalmente, «no lo hicimos y no lo perdimos porque no sabíamos que alguna vez existiría».Sí, es fácil ver el beneficio después de los hechos, pero asumir que todos estaban parados, pateando los talones, esperando la transmisión automática o las plantillas de C ++, realmente no es cierto.
A la pregunta, «¿cómo cambiaban de marcha los conductores antes de que se inventara la transmisión automática?», Uno puede responder razonablemente «manualmente», y ese es el tipo de respuestas que está obteniendo aquí. Incluso puede ser el tipo de pregunta que quería hacer.
Pero no fue la que hizo.
Entonces:
P: ¿Cómo usaban las personas las plantillas antes de que se inventaran?
R: Nosotros no lo hicimos.
P: ¿Cómo usaban las personas las plantillas antes de que se inventaran las plantillas, cuando necesitaban usar plantillas ?
R: Nosotros no necesitábamos usarlas. ¿Por qué asumir que lo hicimos? (¿Por qué asumir que sí?)
P: ¿Cuáles son las formas alternativas de lograr los resultados que proporcionan las plantillas?
R: Existen muchas buenas respuestas arriba.
Por favor, piense en las falacias lógicas en sus publicaciones antes de publicar.
[¡Gracias! Por favor, no hay intención de hacer daño aquí.]
Comentarios
Responder
Respuesta
Como ya dijo Robert Harvey, un puntero vacío es el tipo de datos genérico.
Un ejemplo de la biblioteca C estándar, cómo ordenar una matriz de double con una ordenación genérica:
double *array = ...; int size = ...; qsort (array, size, sizeof (double), compare_doubles);
Donde compare_double
se define como:
int compare_doubles (const void *a, const void *b) { const double *da = (const double *) a; const double *db = (const double *) b; return (*da > *db) - (*da < *db); }
La firma de qsort
se define en stdlib.h:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *) );
Tenga en cuenta que no hay ningún tipo comprobando en tiempo de compilación, ni siquiera en tiempo de ejecución. Si ordena una lista de cadenas con el comparador anterior que espera dobles, felizmente intentará interpretar la representación binaria de una cadena como un doble y ordenar en consecuencia.
Respuesta
Una forma de hacer esto es así:
https://github.com/rgeminas/gp–/blob/master/src/scope/darray.h
#define DARRAY_DEFINE(name, type) DARRAY_TYPEDECL(name, type) DARRAY_IMPL(name, type) // This is one single long line #define DARRAY_TYPEDECL(name, type) \ typedef struct darray_##name \ { \ type* base; \ size_t allocated_mem; \ size_t length; \ } darray_##name; // This is also a single line #define DARRAY_IMPL(name, type) \ static darray_##name* darray_init_##name() \ { \ darray_##name* arr = (darray_##name*) malloc(sizeof(darray_##name)); \ arr->base = (type*) malloc(sizeof(type)); \ arr->length = 0; \ arr->allocated_mem = 1; \ return arr; \ }
La macro DARRAY_TYPEDECL crea efectivamente una definición de estructura (en una sola línea), reemplazando name
con el nombre que pasa, y almacena una matriz del type
que pasa (el name
está ahí para que pueda concatenarlo al nombre de la estructura base y todavía tener un identificador válido – darray_int * no es un nombre válido para una estructura), mientras que la macro DARRAY_IMPL define las funciones que operan en esa estructura (en ese caso, están marcadas como estáticas solo para que uno pueda solo llame a la definición una vez y no separe todo).
Esto se usaría como:
#include "darray.h" // No types have been defined yet DARRAY_DEFINE(int_ptr, int*) // by this point, the type has been declared and its functions defined darray_int_ptr* darray = darray_int_ptr_init();
Respuesta
Creo que las plantillas se utilizan mucho como una forma de reutilizar tipos de contenedores que tienen un muchos valores algorítmicos como matrices dinámicas (vectores), mapas, árboles, etc. ordenación, etc.
Sin plantillas, estos contienen necesariamente implementaciones, están escritas de forma genérica y se les da la información suficiente sobre el tipo requerido para su dominio. Por ejemplo, con un vector, solo necesitan que los datos sean blt «capaces y necesitan saber el tamaño de cada elemento.
Digamos que tiene una clase de contenedor llamada Vector que hace esto. Se necesita nulo *. El uso simplista de esto sería que el código de la capa de la aplicación hiciera mucho casting. Entonces, si están administrando objetos Cat, tienen que lanzar Cat * to void * y regresar por todos lados. Tirar el código de la aplicación con yesos tiene problemas obvios.
Las plantillas resuelven esto.
Otra forma de resolverlo es crear un tipo de contenedor personalizado para el tipo que está almacenando en el contenedor. Entonces, si tiene una clase Cat, crearía una clase CatList derivada de Vector.Luego, sobrecarga los pocos métodos que usa, presentando versiones que toman objetos Cat en lugar de void *. Así que «sobrecargaría el método Vector :: Add (void *) con Cat :: Add (Cat *), que internamente simplemente pasa el parámetro a Vector :: Add (). Luego, en el código de su aplicación,» llamaría al versión sobrecargada de Add al pasar un objeto Cat y así evitar el casting. Para ser justos, el método Add no requeriría una conversión porque un objeto Cat * se convierte en void * sin una conversión. Pero el método para recuperar un elemento, como la sobrecarga de índice o un método Get () sí lo haría.
El otro enfoque, el único ejemplo que recuerdo de principios de los 90 con un gran marco de aplicación, es usar una utilidad personalizada que crea estos tipos sobre clases. Creo que MFC hizo esto. Tenían diferentes clases para contenedores. como CStringArray, CRectArray, CDoulbeArray, CIntArray, etc. En lugar de mantener el código duplicado, hicieron algún tipo de metaprogramación similar a las macros, utilizando una herramienta externa que generaría las clases. Pusieron la herramienta a disposición con Visual C ++ en caso de que alguien quería usarlo, nunca lo hice. Quizás debería haberlo hecho. Pero en ese momento, los expertos estaban promocionando, «Un subconjunto sano de C ++» y «No» necesitas plantillas «