J'essaie d'implémenter une structure queue
en utilisant C. Mon implémentation est très simple; la file d'attente ne peut contenir que int
s et rien d'autre. Je me demandais si je pouvais simuler C++
modèles dans C
(probablement en utilisant le préprocesseur #define
) afin que mon queue
puisse contenir n'importe quel type de données.
Note: Je ne veux pas utiliser void*
. Je pense que c'est un peu risqué et peut facilement provoquer des erreurs d'exécution bizarres.
Vous pouvez utiliser des astuces subtiles et laides pour créer ce type de modèles. Voici ce que je ferais:
Je créerais d'abord une macro - appelons-la par exemple define_list(type)
- qui créerait toutes les fonctions pour une liste d'un type donné. Je créerais ensuite une structure globale contenant des pointeurs de fonction vers toutes les fonctions de la liste, puis j'aurais un pointeur vers cette structure globale dans chaque instance de la liste (notez à quel point elle est similaire à une table de méthode virtuelle ). Ce genre de chose:
#define define_list(type) \
\
struct _list_##type; \
\
typedef struct \
{ \
int (*is_empty)(const struct _list_##type*); \
size_t (*size)(const struct _list_##type*); \
const type (*front)(const struct _list_##type*); \
void (*Push_front)(struct _list_##type*, type); \
} _list_functions_##type; \
\
typedef struct _list_elem_##type \
{ \
type _data; \
struct _list_elem_##type* _next; \
} list_elem_##type; \
\
typedef struct _list_##type \
{ \
size_t _size; \
list_elem_##type* _first; \
list_elem_##type* _last; \
_list_functions_##type* _functions; \
} List_##type; \
\
List_##type* new_list_##type(); \
bool list_is_empty_##type(const List_##type* list); \
size_t list_size_##type(const List_##type* list); \
const type list_front_##type(const List_##type* list); \
void list_Push_front_##type(List_##type* list, type elem); \
\
bool list_is_empty_##type(const List_##type* list) \
{ \
return list->_size == 0; \
} \
\
size_t list_size_##type(const List_##type* list) \
{ \
return list->_size; \
} \
\
const type list_front_##type(const List_##type* list) \
{ \
return list->_first->_data; \
} \
\
void list_Push_front_##type(List_##type* list, type elem) \
{ \
... \
} \
\
_list_functions_##type _list_funcs_##type = { \
&list_is_empty_##type, \
&list_size_##type, \
&list_front_##type, \
&list_Push_front_##type, \
}; \
\
List_##type* new_list_##type() \
{ \
List_##type* res = (List_##type*) malloc(sizeof(List_##type)); \
res->_size = 0; \
res->_first = NULL; \
res->_functions = &_list_funcs_##type; \
return res; \
}
#define List(type) \
List_##type
#define new_list(type) \
new_list_##type()
Voici quelques macros qui appellent simplement les fonctions de la liste via les pointeurs de fonction stockés:
#define is_empty(collection) \
collection->_functions->is_empty(collection)
#define size(collection) \
collection->_functions->size(collection)
#define front(collection) \
collection->_functions->front(collection)
#define Push_front(collection, elem) \
collection->_functions->Push_front(collection, elem)
Notez que si vous utilisez la même structure pour concevoir d'autres collections que des listes, vous pourrez utiliser les dernières fonctions pour toutes les collections qui stockent les bons pointeurs.
Et pour conclure, un petit exemple de la façon d'utiliser notre nouveau modèle de liste:
/* Define the data structures you need */
define_list(int)
define_list(float)
int main()
{
List(int)* a = new_list(int);
List(float)* b = new_list(float);
Push_front(a, 5);
Push_front(b, 5.2);
}
Vous pouvez utiliser cette quantité d'astuces si vous voulez vraiment avoir une sorte de modèles en C, mais c'est plutôt moche (utilisez simplement C++, ce sera plus simple). La seule surcharge sera un pointeur de plus par instance de structure de données, et donc une indirection de plus chaque fois que vous appelez une fonction (aucune conversion n'est effectuée, vous n'avez pas à stocker void*
pointeurs, ouais\o /). J'espère que vous n'utiliserez jamais ça: p
Il y a bien sûr certaines limites car nous utilisons de simples macros de remplacement de texte, et non de vrais modèles.
Vous ne pouvez définir chaque type qu'une seule fois par unité de compilation, sinon, votre programme ne pourra pas être compilé. Cela peut être un inconvénient majeur par exemple si vous écrivez une bibliothèque et que certains de vos en-têtes contiennent des define_
instructions.
Si vous souhaitez créer un List
dont le type de modèle est composé de plusieurs mots (signed char
, unsigned long
, const bar
, struct foo
...) ou dont le type de modèle est un pointeur (char*
, void*
...), vous devrez d'abord typedef
ce type.
define_list(int) /* OK */
define_list(char*) /* Error: pointer */
define_list(unsigned long) /* Error: several words */
typedef char* char_ptr;
typedef unsigned long ulong;
define_list(char_ptr) /* OK */
define_list(ulong) /* OK */
Vous devrez recourir à la même astuce si vous souhaitez créer des listes imbriquées.
Eh bien, la seule possibilité qui me vient à l'esprit sont les macros (#define
s). Peut-être quelque chose comme:
queue.h:
#define TYPE int
#define TYPED_NAME(x) int_##x
#include "queue_impl.h"
#undef TYPE
#undef TYPED_NAME
#define TYPE float
#define TYPED_NAME(x) float_##x
#include "queue_impl.h"
#undef TYPE
#undef TYPED_NAME
...
queue_impl.h:
//no include guard, of course
typedef struct
{
TYPE *data;
...
} TYPED_NAME(queue);
void TYPED_NAME(queue_insert) (TYPED_NAME(queue) *queue, TYPE data)
{
...
}
Si cela fonctionne (dont je ne suis pas sûr à 100%, n'étant pas un tel expert du préprocesseur), il devrait vous donner les structures int_queue
et float_queue
, ainsi que les fonctions
void int_queue_insert(int_queue *queue, int data);
void float_queue_insert(float_queue *queue, float data);
Bien sûr, vous devrez faire vous-même l'instanciation du "modèle" pour tous les types dont vous avez besoin, mais cela revient à répéter le bloc de 5 lignes dans queue.h
. L'implémentation réelle ne doit être écrite qu'une seule fois. Bien sûr, vous pouvez affiner encore plus cela, mais l'idée de base doit être claire.
Cela vous donnera au moins des modèles de file d'attente parfaitement sûrs pour le type, bien que dépourvu de la commodité d'interfaces complètement correspondantes (les fonctions doivent porter le nom du type, car C ne prend pas en charge les fonctions surchargées).
Implémentez une file d'attente contenant des données void * et interprétez ce void * comme un pointeur vers n'importe quel type, ou même un type primitif comme int.
Utiliser #define est possible, mais pensez au débogage, si quelque chose ne va pas ...
Voici une version qui peut vous permettre d'instancier (via le préprocesseur) et d'utiliser plusieurs types dans le même fichier C (Attention, il utilise concaténation de jetons ):
#include <stdio.h>
#define DEFINE_LL_NODE(CONCRETE_TYPE) \
struct node_of_ ## CONCRETE_TYPE \
{ \
CONCRETE_TYPE data; \
struct node_of_ ## CONCRETE_TYPE *next; \
};
#define DECLARE_LL_NODE(CONCRETE_TYPE,VARIABLE_NAME) \
struct node_of_ ## CONCRETE_TYPE VARIABLE_NAME;
/* Declarations for each type. */
DEFINE_LL_NODE(int)
DEFINE_LL_NODE(char)
int main (void)
{
/* Declaration of instances of each type. */
DECLARE_LL_NODE (int, foo)
DECLARE_LL_NODE (char, bar)
/* And you can then use these instances. */
foo.data = 1;
foo.next = NULL;
bar.data = 'c';
bar.next = NULL;
}
Si je le prétraite avec cpp
, j'obtiens:
struct node_of_int { int data; struct node_of_int *next; };
struct node_of_char { char data; struct node_of_char *next; };
int main (void)
{
struct node_of_int foo;
struct node_of_char bar;
foo.data = 1;
foo.next = ((void *)0);
bar.data = 'c';
bar.next = ((void *)0);
}
Je me posais cette question depuis longtemps mais j'ai maintenant une réponse définitive que tout le monde peut comprendre; alors voici!
Lorsque je suivais un cours sur les structures de données, j'ai dû lire le livre de Standish sur les structures de données, les algorithmes en C; c'était douloureux; il n'avait pas de génériques, il était plein de mauvaises notations et de tas de mutations d'État mondiales où il n'y avait aucune garantie d'être là; Je savais qu'adopter son style de code signifiait visser tous mes futurs projets, mais je savais qu'il y avait une meilleure façon, alors voici, la meilleure:
Voici à quoi il ressemblait avant de le toucher (en fait, je l'ai touché de toute façon pour le rendre formaté de manière à ce que les humains puissent le lire, vous êtes les bienvenus); c'est vraiment moche et faux à plusieurs niveaux, mais je vais le lister pour référence:
#include <stdio.h>
#define MaxIndex 100
int Find(int A[])
{
int j;
for (j = 0; j < MaxIndex; ++j) {
if (A[j] < 0) {
return j;
}
}
return -1;
}
int main(void)
{
// reminder: MaxIndex is 100.
int A[MaxIndex];
/**
* anonymous scope #1
* initialize our array to [0..99],
* then set 18th element to its negative value(-18)
* to make the search more interesting.
*/
{
// loop index, nothing interesting here.
int i;
// initialize our array to [0..99].
for (i = 0; i < MaxIndex; ++i) {
A[i] = i * i;
}
A[17]= -A[17];
}
/**
* anonymous scope #2
* find the index of the smallest number and print it.
*/
{
int result = Find(A);
printf(
"First negative integer in A found at index = %d.\n",
result
);
}
// wait for user input before closing.
getchar();
return 0;
}
Ce programme fait plusieurs choses dans un style horriblement mauvais; En particulier, il définit une macro globale qui n'est utilisée que dans une seule étendue, mais persiste ensuite à polluer tout code à partir de maintenant; très mauvais, et provoque l'échelle de l'API Windows de la pollution de portée mondiale à grande échelle.
De plus, ce programme passe l'argument sous forme de tableau sans struct pour le contenir; en d'autres termes, le tableau est mort à l'arrivée une fois qu'il a atteint la fonction Find; nous ne connaissons plus la taille du tableau, nous avons donc maintenant main et Find dépendent d'une macro globale, très mauvais.
Il existe deux façons par force brute de faire disparaître ce problème tout en gardant le code simple; la première façon est de créer une structure globale qui définit le tableau comme un tableau de 100 entiers; De cette façon, le passage de la structure préservera la longueur du tableau qu'il contient. La deuxième façon consiste à passer la longueur du tableau comme argument de find, et à n'utiliser que la #definir la ligne avant de créer le tableau, et #undef juste après, car la portée connaîtra toujours la taille du tableau via sizeof (A)/sizeof (A [0]) qui a 0 surcharge d'exécution, le compilateur en déduira 100 et le collera.
Pour résoudre ce problème d'une troisième manière, j'ai créé un en-tête qui joue Nice pour créer des tableaux génériques; c'est un type de données abstrait, mais je voudrais l'appeler une structure de données automatisée.
SimpleArray.h
/**
* Make sure that all the options needed are given in order to create our array.
*/
#ifdef OPTION_UNINSTALL
#undef OPTION_ARRAY_TYPE
#undef OPTION_ARRAY_LENGTH
#undef OPTION_ARRAY_NAME
#else
#if (!defined OPTION_ARRAY_TYPE) || !defined OPTION_ARRAY_LENGTH || (!defined OPTION_ARRAY_NAME)
#error "type, length, and name must be known to create an Array."
#endif
/**
* Use the options to create a structure preserving structure for our array.
* that is, in contrast to pointers, raw arrays.
*/
struct {
OPTION_ARRAY_TYPE data[OPTION_ARRAY_LENGTH];
} OPTION_ARRAY_NAME;
/**
* if we are asked to also zero out the memory, we do it.
* if we are not granted access to string.h, brute force it.
*/
#ifdef OPTION_ZERO_MEMORY
#ifdef OPTION_GRANT_STRING
memset(&OPTION_ARRAY_NAME, 0, OPTION_ARRAY_LENGTH * sizeof(OPTION_ARRAY_TYPE));
#else
/* anonymous scope */
{
int i;
for (i = 0; i < OPTION_ARRAY_LENGTH; ++i) {
OPTION_ARRAY_NAME.data[i] = 0;
}
}
#endif
#undef OPTION_ZERO_MEMORY
#endif
#endif
Cet en-tête est essentiellement ce à quoi devrait ressembler chaque en-tête de structure de données C si vous êtes obligé d'utiliser le préprocesseur C (contrairement à PHP/Templating toolkit/ASP/votre propre langage de script embarquable, que ce soit LISP).
Prenons-le pour un tour:
#include <stdio.h>
int Find(int A[], int A_length)
{
int j;
for (j = 0; j < A_length; ++j) {
if (A[j] < 0) {
return j;
}
}
return -1;
}
int main(void)
{
// std::array<int, 100> A;
#define OPTION_ARRAY_TYPE int
#define OPTION_ARRAY_LENGTH 100
#define OPTION_ARRAY_NAME A
#include "SimpleArray.h"
/**
* anonymous scope #1
* initialize our array to [0..99],
* then set 18th element to its negative value(-18)
* to make the search more interesting.
*/
{
// loop index, nothing interesting here.
int i;
// initialize our array to [0..99].
for (i = 0; i < (sizeof(A.data) / sizeof(A.data[0])); ++i) {
A.data[i] = i * i;
}
A.data[17]= -A.data[17];
}
/**
* anonymous scope #2
* find the index of the smallest number and print it.
*/
{
int result = Find(A.data, (sizeof(A.data) / sizeof(A.data[0])));
printf(
"First negative integer in A found at index = %d.\n",
result
);
}
// wait for user input before closing.
getchar();
// making sure all macros of SimpleArray do not affect any code
// after this function; macros are file-wide, so we want to be
// respectful to our other functions.
#define OPTION_UNINSTALL
#include "SimpleArray.h"
return 0;
}
BEHOLD, nous avons inventé un std :: array naïf en préprocesseur C et C pur! Nous avons utilisé des macros, mais nous ne sommes pas méchants, car nous nettoyons après nous-mêmes! Toutes nos macros sont indéfinies à la fin de notre périmètre.
Il ya un problème; nous ne connaissons plus la taille du tableau, sauf si nous le faisons (sizeof(A.data) / sizeof(A.data[0]))
. Cela n'a pas de surcharge pour le compilateur, mais ce n'est pas adapté aux enfants; les macros non plus, mais nous travaillons ici dans la boîte; nous pouvons plus tard utiliser un préprocesseur plus convivial comme PHP pour le rendre convivial pour les enfants.
Pour résoudre ce problème, nous pouvons créer une bibliothèque d'utilitaires qui agit comme des méthodes sur notre structure de données de tableau "libre".
SimpleArrayUtils.h
/**
* this is a smart collection that is created using options and is
* removed from scope when included with uninstall option.
*
* there are no guards because this header is meant to be strategically
* installed and uninstalled, rather than kept at all times.
*/
#ifdef OPTION_UNINSTALL
/* clean up */
#undef ARRAY_FOREACH_BEGIN
#undef ARRAY_FOREACH_END
#undef ARRAY_LENGTH
#else
/**
* array elements vary in number of bytes, encapsulate common use case
*/
#define ARRAY_LENGTH(A) \
((sizeof A.data) / (sizeof A.data[0]))
/**
* first half of a foreach loop, create an anonymous scope,
* declare an iterator, and start accessing the items.
*/
#if defined OPTION_ARRAY_TYPE
#define ARRAY_FOREACH_BEGIN(name, iter, arr)\
{\
unsigned int iter;\
for (iter = 0; iter < ARRAY_LENGTH(arr); ++iter) {\
OPTION_ARRAY_TYPE name = arr.data[iter];
#endif
/**
* second half of a foreach loop, close the loop and the anonymous scope
*/
#define ARRAY_FOREACH_END \
}\
}
#endif
Il s'agit d'une bibliothèque assez riche en fonctionnalités, qui exporte essentiellement
ARRAY_LENGTH :: Tout avec un champ de données -> int
et si nous avons encore OPTION_ARRAY_SIZE défini ou redéfini, l'en-tête définit également comment faire une boucle foreach; ce qui est mignon.
Maintenant, devenons fous:
SimpleArray.h
/**
* Make sure that all the options needed are given in order to create our array.
*/
#ifdef OPTION_UNINSTALL
#ifndef OPTION_ARRAY_TYPE
#undef OPTION_ARRAY_TYPE
#endif
#ifndef OPTION_ARRAY_TYPE
#undef OPTION_ARRAY_LENGTH
#endif
#ifndef OPTION_ARRAY_NAME
#undef OPTION_ARRAY_NAME
#endif
#ifndef OPTION_UNINSTALL
#undef OPTION_UNINSTALL
#endif
#else
#if (!defined OPTION_ARRAY_TYPE) || !defined OPTION_ARRAY_LENGTH || (!defined OPTION_ARRAY_NAME)
#error "type, length, and name must be known to create an Array."
#endif
/**
* Use the options to create a structure preserving structure for our array.
* that is, in contrast to pointers, raw arrays.
*/
struct {
OPTION_ARRAY_TYPE data[OPTION_ARRAY_LENGTH];
} OPTION_ARRAY_NAME;
/**
* if we are asked to also zero out the memory, we do it.
* if we are not granted access to string.h, brute force it.
*/
#ifdef OPTION_ZERO_MEMORY
#ifdef OPTION_GRANT_STRING
memset(&OPTION_ARRAY_NAME, 0, OPTION_ARRAY_LENGTH * sizeof(OPTION_ARRAY_TYPE));
#else
/* anonymous scope */
{
int i;
for (i = 0; i < OPTION_ARRAY_LENGTH; ++i) {
OPTION_ARRAY_NAME.data[i] = 0;
}
}
#endif
#undef OPTION_ZERO_MEMORY
#endif
#endif
SimpleArrayUtils.h
/**
* this is a smart collection that is created using options and is
* removed from scope when included with uninstall option.
*
* there are no guards because this header is meant to be strategically
* installed and uninstalled, rather than kept at all times.
*/
#ifdef OPTION_UNINSTALL
/* clean up, be mindful of undef warnings if the macro is not defined. */
#ifdef ARRAY_FOREACH_BEGIN
#undef ARRAY_FOREACH_BEGIN
#endif
#ifdef ARRAY_FOREACH_END
#undef ARRAY_FOREACH_END
#endif
#ifdef ARRAY_LENGTH
#undef ARRAY_LENGTH
#endif
#else
/**
* array elements vary in number of bytes, encapsulate common use case
*/
#define ARRAY_LENGTH(A) \
((sizeof A.data) / (sizeof A.data[0]))
/**
* first half of a foreach loop, create an anonymous scope,
* declare an iterator, and start accessing the items.
*/
#if defined OPTION_ARRAY_TYPE
#define ARRAY_FOREACH_BEGIN(name, iter, arr)\
{\
unsigned int iter;\
for (iter = 0; iter < ARRAY_LENGTH(arr); ++iter) {\
OPTION_ARRAY_TYPE name = arr.data[iter];
#endif
/**
* second half of a foreach loop, close the loop and the anonymous scope
*/
#define ARRAY_FOREACH_END \
}\
}
#endif
main.c
#include <stdio.h>
// std::array<int, 100> A;
#define OPTION_ARRAY_TYPE int
#define OPTION_ARRAY_LENGTH 100
#define OPTION_ARRAY_NAME A
#include "SimpleArray.h"
#define OPTION_UNINSTALL
#include "SimpleArray.h"
int Find(int A[], int A_length)
{
int j;
for (j = 0; j < A_length; ++j) {
if (A[j] < 0) {
return j;
}
}
return -1;
}
int main(void)
{
#define OPTION_ARRAY_NAME A
#define OPTION_ARRAY_LENGTH (sizeof(A.data) / sizeof(A.data[0]))
#define OPTION_ARRAY_TYPE int
#include "SimpleArray.h"
/**
* anonymous scope #1
* initialize our array to [0..99],
* then set 18th element to its negative value(-18)
* to make the search more interesting.
*/
{
#include "SimpleArrayUtils.h"
printf("size: %d.\n", ARRAY_LENGTH(A));
ARRAY_FOREACH_BEGIN(item, i, A)
A.data[i] = i * i;
ARRAY_FOREACH_END
A.data[17] = -A.data[17];
// uninstall all macros.
#define OPTION_UNINSTALL
#include "SimpleArrayUtils.h"
}
/**
* anonymous scope #2
* find the index of the smallest number and print it.
*/
{
#include "SimpleArrayUtils.h"
int result = Find(A.data, (sizeof(A.data) / sizeof(A.data[0])));
printf(
"First negative integer in A found at index = %d.\n",
result
);
// uninstall all macros.
#define OPTION_UNINSTALL
#include "SimpleArrayUtils.h"
}
// wait for user input before closing.
getchar();
// making sure all macros of SimpleArray do not affect any code
// after this function; macros are file-wide, so we want to be
// respectful to our other functions.
#define OPTION_UNINSTALL
#include "SimpleArray.h"
return 0;
}
Comme vous pouvez le voir; nous avons maintenant le pouvoir d'exprimer des abstractions libres (le compilateur les remplace pour nous), nous ne payons que ce dont nous avons besoin (les structures), et le reste est jeté et ne pollue pas la portée mondiale.
J'insiste sur la puissance de PHP ici parce que peu l'ont vu en dehors du contexte des documents HTML; mais vous pouvez l'utiliser dans des documents C ou tout autre fichier texte. Vous pouvez utiliser Templating Toolkit pour avoir n'importe quel langage de script que vous aimez mettre dans les macros pour vous; et ces langages seront bien meilleurs que le préprocesseur C car ils ont des espaces de noms, des variables et des fonctions réelles; cela les rend plus faciles à déboguer puisque vous déboguez un script réel qui génère le code; pas le préprocesseur C qui est un enfer à déboguer, en grande partie à cause de la familiarité (qui dans le bon esprit passe des heures à jouer avec et se familiariser avec le préprocesseur C? peu le font).
Voici un exemple de cela avec PHP:
SimpleArray.php
<?php
class SimpleArray {
public $length;
public $name;
public $type;
function __construct($options) {
$this->length = $options['length'];
$this->name = $options['name'];
$this->type = $options['type'];
}
function getArray() {
echo ($this->name . '.data');
}
function __toString() {
return sprintf (
"struct {\n" .
" %s data[%d];\n" .
"} %s;\n"
,
$this->type,
$this->length,
$this->name
);
}
};
?>
main.php
#include <stdio.h>
<?php include('SimpleArray.php'); ?>
int Find(int *A, int A_length)
{
int i;
for (i = 0; i < A_length; ++i)
{
if (A[i] < 0) {
return i;
}
}
return -1;
}
int main(int argc, char **argv)
{
<?php
$arr = new SimpleArray(array(
'name' => 'A',
'length' => 100,
'type' => 'int'
));
echo $arr;
?>
printf("size of A: %d.\n", <?php echo($arr->length); ?>);
/* anonymous scope */
{
int i;
for (i = 0; i < <?php echo($arr->length)?>; ++i) {
<?php $arr->getArray(); ?>[i] = i * i;
}
<?php $arr->getArray(); ?>[17] = -<?php $arr->getArray()?>[17];
}
int result = Find(<?php $arr->getArray();?>, <?php echo $arr->length; ?>);
printf(
"First negative integer in A found at index = %d.\n",
result
);
getchar();
return 0;
}
courir php main.php > main.c
puis
gcc main.c -o main
./main
Cela ressemble beaucoup à Objective C, car c'est essentiellement ce que fait Objective C, sauf qu'il a tendance à lier les "macros" de compilation à un temps d'exécution réel (comme si php était disponible au moment de l'exécution pendant le temps où C était en cours d'exécution, et dans tournez votre C peut parler à php et php peut parler à C, sauf que le php est le langage smalltalkish avec de nombreux crochets). La principale différence est que l'Objectif C n'a pas à ma connaissance un moyen de faire des constructions "statiques", comme nous l'avons fait ici; ses objets sont en fait d'exécution, et en tant que tels, ils sont beaucoup plus chers à accéder, mais sont beaucoup plus flexibles et préservent la structure, tandis que les structures C se réduisent en octets dès que l'en-tête quitte la portée (alors que les objets peuvent être renvoyés à leur état d'origine). État en utilisant les syndicats étiquetés internes) ...
Si vous vraiment voulez le faire, cela pourrait être résolu par un simple typedef
:
typedef int data_t;
struct queue
{
data_t* data;
}
Vous pouvez maintenant utiliser data_t
dans tous les endroits au lieu de plain int
s. Notez, cependant, que vous ne pourrez pas utiliser plusieurs types à la fois (au moins, je ne vois pas comment ce comportement particulier des modèles C++ peut être simulé en C simple).
Utilisez l'une des macros de génération de code dans une autre réponse, puis terminez-la avec des macros de surcharge C11 afin de ne pas avoir à surcharger vos sites d'appels avec trop d'informations de type.
Vous ne pouvez pas vraiment obtenir un modèle de haute qualité en C avec des macros de préprocesseur; car, ces macros ne se développent qu'une seule fois, donc au mieux vous pouvez obtenir une structure de données qui peut être retapée, mais une fois traitée, c'est ce type pour tout le programme.
Cela signifie que vous devez considérer void *
solutions de type, qui affaiblissent la vérification de type de C. Pour tenter de corriger la vérification de type affaibli, envisagez d'incorporer un champ "type" dans votre structure qui est une chaîne "assign once at construction" qui représente le type non void *. Ensuite, vous pouvez éventuellement améliorer le manque de vérification de type dans les fonctions liées à la maintenance de la structure. Autrement dit, si une telle chose est même importante pour vous.