2010-12-02 89 views
7

Il est possible de faire certains types de types génériques-fonctions comme les macros dans C, pour les choses exemple comme:Programmation de type générique avec des macros: astuces pour déterminer le type?

#define SQRT(x) (sizeof(x) == sizeof(float) ? sqrtf((x)) : \ 
       sizeof(x) == sizeof(double) ? sqrt((x)) : \ 
       sqrtl((x))) 

Cela fonctionne (la plupart du temps) comme prévu aussi longtemps que x est un type à virgule flottante. Et si je veux une macro générique de type qui peut prendre un type entier ou un type de pointeur, qui peut avoir la même taille. Existe-t-il un moyen intelligent de tester si l'argument macro est un entier ou un pointeur? Qu'en est-il d'un entier par rapport à un type à virgule flottante?

Répondre

3

Les macros ne connaissent pas les types. Ils effectuent un copier-coller littéral du #define. La sécurité de type n'existe tout simplement pas ici.

C n'est pas un langage fortement typé dans un sens significatif. Si vous voulez un peu de sécurité de type, utilisez C++, où vous pouvez accomplir un peu avec des modèles et une surcharge de fonctions.

+0

Les macros ne sont pas intrinsèquement connues, mais il y a parfois des astuces. Par exemple, '((0 * (x) -1) <0)' peut déterminer si 'x' est signé ou non (étant donné que' x' a un rang de conversion de 'int' ou plus). –

0

Il est possible d'avoir une sorte de système de contrôle de type, mais il est vraiment une bidouille en C.

glib ce que cela; vous pouvez jeter un oeil à la façon dont ils le font, ou peut-être l'utiliser vous-même (c'est une bibliothèque C astucieuse à avoir autour de toute façon).

3

Votre résultat n'est pas vraiment type générique, car le résultat est toujours long double, peu importe quel type d'argument est passé - le type de résultat de ?: lorsque les deuxième et troisième opérandes sont des types arithmétiques est le type qui aurait résultat de l'application des conversions arithmétiques habituelles à ces opérandes. Pour qu'il en soit ainsi, vous pouvez utiliser l'extension de GCC typeof:

#define SQRT(x) (__typeof__ (x))(sizeof(x) == sizeof(float) ? sqrtf((x)) : \ 
       sizeof(x) == sizeof(double) ? sqrt((x)) : \ 
       sqrtl((x))) 

entier par rapport à virgule flottante peut également être effectuée à l'aide typeof:

(__typeof__ (X))1.1 == 1 

Je ne peux pas penser à une façon de faire integer- contre-pointeur. Les techniques décrites sur this page sont assez intéressantes, cependant.

3

Vous pouvez détecter si une expression est une expression entière ou une expression char*, au moins sur les architectures où cast du pointeur vers uintptr_t est bien défini:

#define INT_OR_CHARP(X) (((uintptr_t)((X)+1) - (uintptr_t)(X)) == 1) 

Cela détecter si X est un pointeur vers un tapez T avec sizeof(T) > 1. Cela ne fonctionnera pas pour void* et d'autres cas d'angle. Et parce que X est évalué deux fois que vous auriez à surveiller les effets secondaires.

Pour éviter des problèmes avec débordement d'entier si X est de type signed int par exemple, vous pouvez remplacer (X) avec

(1 ? (X) : (uintmax_t)0) 

Cela garantit que si X est une expression entière ce sera de type uintmax_t. Le +1 sera alors peut-être enrouler autour, mais le résultat est toujours bien défini et la différence entre les deux parties sera toujours .Si X est une expression de pointeur, cela est dû au fait que toute expression entière constante de valeur 0 est également une constante de pointeur .

Au total, cela donne

#define INT_OR_CHARP(X) (((uintptr_t)((1 ? (X) : (uintmax_t)0)+1) - (uintptr_t)(1 ? (X) : (uintmax_t)0)) == 1) 
+0

Je ne vois pas comment '(1? (X): (uintmax_t) 0)' est utile. Est-ce une sorte de truc avec l'opérateur '?:' Forçant les types? Les types non signés ne débordent pas non plus. –

+0

@R: 'X' peut être de n'importe quel type entier. Donc, si c'était 'int', disons,' (X) + 1' pourrait déborder. Si c'est un type entier (signé ou non) '(1? (X): (uintmax_t) 0)' est toujours de type 'uintmax_t' mais avec la valeur' X'. Si 'X' est de type pointeur, cette expression sera toujours du même type de pointeur. –

1

Le mot-clé _Generic a été ajouté dans le C11 standard à cet effet.

Cela fonctionne comme une instruction switch pour les types d'expression.

Votre exemple peut être écrit en utilisant ce mot-clé comme ceci:

#define SQRT(X) _Generic((X), 
    float: sqrtf, \ 
    double: sqrt, \ 
    default: sqrtl \ 
)(X) 

GCC prend en charge ce mot-clé depuis version 4.9.