Reputation: 2967
It's well known the use of typeof
in Macros to make them type independent, such as container_of()
and many other macros from the Linux kernel. It is unarguable that the typeof
keyword unleashes a lot of power when used in these macros.
This question is about further use of the typeof
keyword. What other contexts could the keyword bring lots of gain in C code, besides Macros?
Upvotes: 3
Views: 2311
Reputation: 9962
Some people (myself included) dislike the syntax of the C++ const_cast<>
operator, because;
const
. But I am wrong: it is not misnamed, since it can also add const
and/or volatile
"cv" qualifiers, and it only partially violates DRY, since the compiler will catch any errors. So I dislike it slightly less and use it: it is safer than the C-style cast.
Using gcc's typeof
, you can have almost the same type safety in C.
The following C code sample gives a CONST_CAST(T, x)
macro, and illustrates its use:
#define REMOVE_QUALIFIER(cv, T, x) /* this macro evaluates its args only once */ \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), cv T), ((T)(x)), \
(void)0)
#define ADD_QUALIFIER(cv, T, x) /* this macro evaluates its args only once */ \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), T), ((cv T)(x)), \
(void)0)
#ifdef __GNUC__
#define CONST_CAST(T, x) REMOVE_QUALIFIER(const, T, x) // "misnamed"
#else
#define CONST_CAST(T, x) ((T)(x)) // fallback to standard C cast
#endif
void foo(void);
void foo(void) {
const int *a = 0;
const float *x = 0;
int *b = a; // warning
int *c = (int *)a; // no warning, unsafe standard cast
int *d = (int *)x; // no warning, and likely wrong
int *e = CONST_CAST(int *, a); // ok
int *f = CONST_CAST(int *, x); // error
unsigned *g = CONST_CAST(unsigned *, a); // error
const int **h = &b; // warning
const int **i = ADD_QUALIFIER(const, int **, &b); // ok
const int **j = ADD_QUALIFIER(const, int **, &x); // error
}
This technique can also be used to change the signedness of a type, reminiscent of C++'s std::make_signed
and std::make_unsigned
, or Boost traits. For example:
#define MAKE_UNSIGNED(T, x) ADD_QUALIFIER(unsigned, T, x) // T usually char*
Upvotes: 1
Reputation: 9962
This use of gcc's typeof
is yet another reinterpret cast, using union-punning.
It can be applied to scalars and structures, as well as to pointers. It gives only an R-value.
#ifdef __GNUC__
#define PUN_CAST(T, x) (((union {typeof(x) src; T dst;})(x)).dst)
#else
#define PUN_CAST(T, x) (*(T*)&(x)) //<-- classic pun: breaks strict aliasing rules
#endif
Caveat: you can use this to cast a pointer into an array of 4 or 8 bytes, or vice versa. But you can't use it to cast a pointer into another pointer, in an attempt to avoid the strict aliasing rules.
Upvotes: 0
Reputation: 9962
A second usage of typeof
is to generate pointers to constants, or pointers to function return values, as shown in the following example:
#include <stdio.h>
#include <time.h>
#include <sys/socket.h>
#define AMPERSAND(x) (&(typeof(x)){x})
int main(void) {
printf("%s\n", ctime(AMPERSAND(time(0)))); // pointer to time_t
setsockopt(0, SOL_SOCKET, SO_REUSEADDR, AMPERSAND(1), sizeof 1);
return 0;
}
This allows for straight-forward function composition, rather than having to save temporaries in named variables. (Unfortunately this doesn't extend to g++.)
Upvotes: 2
Reputation: 9962
One use of typeof
is to const-cast a 2-dimensional array. In gcc, the construct:
extern void foo(const int a[2][2]); // or equivalently a[][2]
int a[2][2];
foo(a);
will generate:
"warning: passing argument 1 of 'foo' from incompatible pointer type".
(See http://c-faq.com/ansi/constmismatch.html for the reason why.) One way to fix this is to use a sledge-hammer-like cast, such as:
foo((void *)a);
Such a cast will happily take whatever you, perhaps mistakenly, give it.
But we can be much more delicate. By using the casting-macro CONST_CAST_2D
given in the following code sample, the warning is eliminated. And more importantly, if you try to apply it to anything other than a 2-D array, you will get a compiler error/warning. CONST_CAST_PP
works similarly, for a pointer-to-a-pointer.
#define CONST_CAST_2D(x) ((const typeof((x)[0][0])(*)[countof((x)[0])])(x))
#define CONST_CAST_PP(x) ((const typeof(**(x))**)(x))
#define countof(x) (sizeof(x) / sizeof 0[x]) // semi-standard define
static void foo(const int a[][2]) {} // takes const
static void bar(const int **b) {} // takes const
int main(void) {
int a[2][2]; // non-const
int **b; // non-const
foo(CONST_CAST_2D(a)); // ok
bar(CONST_CAST_PP(b)); // ok
return 0;
}
CONST_CAST_PP
provides a clean and robust solution to a commonly-asked problem, e.g.:
And CONST_CAST_2D
resolves:
Upvotes: 2