8

It's possible to do certain types of type-generic-functions as macros in C, for instance things like:

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

This works (mostly) as expected as long as x is a floating point type.

But what if I want a type-generic macro that can take either an integer type or a pointer type, which might have the same size. Is there a clever way to test whether the macro argument is an integer or a pointer? What about an integer versus a floating point type?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711

5 Answers5

3

You may detect if an expression is an integer expression or a char* expression, at least on architectures where cast from pointer to uintptr_t is well defined:

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

This will detect if X is a pointer to a type T with sizeof(T) > 1. This will not work for void* and other corner cases. And because X is evaluated two times you'd have to watch for side effects.

To avoid problems with integer overflow if X is e.g of type signed int you may replace (X) with

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

This guarantees that if X is an integer expression this will be of type uintmax_t. The +1 will then perhaps wrap around, but the result is always well defined and the difference between the two parts will always be 1. If X is a pointer expression, then this is so because any constant integer expression of value 0 is also a null pointer constant.

In total this gives

#define INT_OR_CHARP(X) (((uintptr_t)((1 ? (X) : (uintmax_t)0)+1) - (uintptr_t)(1 ? (X) : (uintmax_t)0)) == 1)
Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • 1
    I don't see how `(1 ? (X) : (uintmax_t)0)` is useful. Is that some sort of trick with the `?:` operator forcing types? Also unsigned types do not overflow. – R.. GitHub STOP HELPING ICE Dec 02 '10 at 13:55
  • @R.: `X` can be of any integer type. So if it were `int`, say, `(X)+1` could overflow. If it is integer type (signed or not) `(1 ? (X) : (uintmax_t)0)` is always of type `uintmax_t` but with value `X`. If `X` is of pointer type that expression will still be of the same pointer type. – Jens Gustedt Dec 02 '10 at 14:18
3

The _Generic keyword was added in the C11 standard for this purpose.

It works like a switch statement for expression types.

Your example can be written using this keyword like this:

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

GCC provides support for this keyword since version 4.9.

Jens
  • 5,767
  • 5
  • 54
  • 69
2

No. Macros do not know what types are. They perform a literal copy-and-paste of the #define. Type safety simply doesn't exist here.

C is not a strongly typed language in any meaningful sense. If you want some modicum of type safety, use C++, where you can accomplish a little with templates and function overloading.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • Macros don't inherently know, but sometimes there are tricks. For instance, `((0*(x)-1) < 0)` can determine if `x` is signed or not (given that `x` has conversion rank of `int` or higher). – R.. GitHub STOP HELPING ICE Dec 02 '10 at 04:06
  • C++ template parameters are duck-typed just like macros (at least prior to concepts). The error will come from whether the expanded code compiles – Justin Meiners Oct 05 '22 at 21:37
2

Your result isn't really type-generic, because the result is always long double regardless of what type of argument is passed - the result type of ?: when the second and third operands are arithmetic types is the type that would result from applying the usual arithmetic conversions to those operands. To make it so, you could use GCC's typeof extension:

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

Integer versus floating-point can also be done using typeof:

(__typeof__ (X))1.1 == 1

I can't think of a way to do integer-versus-pointer. The techniques described on this page are quite interesting, though.

caf
  • 233,326
  • 40
  • 323
  • 462
0

It is possible to have some sort of type checking system, but it really is a kludge in C.

glib does this; you can have a look at how they do it, or possibly use it yourself (it's a nifty C library to have around anyway).

El Yobo
  • 14,823
  • 5
  • 60
  • 78