Felipe Lavratti
Felipe Lavratti

Reputation: 2967

typeof uses in C, besides macros

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

Answers (4)

Joseph Quinsey
Joseph Quinsey

Reputation: 9962

Some people (myself included) dislike the syntax of the C++ const_cast<> operator, because;

  • It seems misnamed, because it removes const.
  • It seems to violate DRY, because it requires a redundant type arg.

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

Joseph Quinsey
Joseph Quinsey

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

Joseph Quinsey
Joseph Quinsey

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

Joseph Quinsey
Joseph Quinsey

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

Related Questions