DEV Community

Paul J. Lucas
Paul J. Lucas

Posted on • Edited on

typeof in C23

#c

Introduction

Among other things, C23 added the typeof and typeof_unqual keywords that return the type of an expression:

int x; // int (obviously) typeof(x) y; // also int 
Enter fullscreen mode Exit fullscreen mode

Note: for this article, I’ll simply use typeof to mean either typeof or typeof_unqual rather than always repeating “or typeof_unqual” unless otherwise stated.

The expression is not evaluated. Like sizeof, typeof is a compile-time operator. typeof standardizes the long-existing gcc extension.

typeof is sort-of like decltype in C++. Why isn’t typeof called decltype in C also? (Or why isn’t decltype in C++ called typeof?) The short answer is that C++ has references and C doesn’t — and that affects the type deduced. There’s also the long answer.

But wait! Since C23 also added auto, why is typeof needed?

  • Variables declared with typeof don’t need initializers.
  • typeof can clarify complicated declarations.

Declarations without Initializers

Using auto always requires an initializer since the compiler deduces the type from the type of the initializer expression; using typeof does not:

double f(void); auto x = f(); // double typeof(f()) y; // double, but without initializer 
Enter fullscreen mode Exit fullscreen mode

Depending on what you’re doing, you may not want to initialize upon declaration, for example if a variable is initialized only conditionally or you need to defer initialization.

Of course you still can use an initializer with typeof if you want to guarantee a type based on something other than the initializer:

typeof(f()) y = g(); // type is what f() returns, not g() 
Enter fullscreen mode Exit fullscreen mode

Clarifying Complicated Declarations

C in infamous for complicated declarations. Normally, complicated declarations can be simplified via typedef. However, typeof can also be used since it can also take a type instead of an expression:

int *p1, *q1; // both pointer to int typeof(int*) p2, q2; // same // array 4 of pointer to function (int) returning double double (*a1[4])(int); typeof(double(int)) *a2[4]; // same // function (void) returning ... // ... pointer to function (int) returning double double (*f1(void))(int); typeof(double(int))* f2(void); // same 
Enter fullscreen mode Exit fullscreen mode

While the typeof declarations are longer, they’re much clearer.

The ability of typeof to take either an expression or a type parallels the ability of sizeof to do the same.

You can even declare macros to create an entirely alternate declaration syntax:

#define ARRAY_OF(T, N) typeof(T[N]) #define POINTER_TO(T) typeof(T*)  ARRAY_OF(POINTER_TO(char const), 4) pc; // char const *pc[4] 
Enter fullscreen mode Exit fullscreen mode

typeof_unqual

The difference between typeof and typeof_unqual is that the latter removes all top-level qualifiers (_Atomic, const, restrict, and volatile):

extern int i; extern int const ci; extern int *pi; extern int const *pci; extern int *const cpi; extern int const *const cpci; typeof (i) i2; // int typeof_unqual(i) i2u; // int typeof (ci) ci2; // int const typeof_unqual(ci) ci2u; // int typeof (pi) pi2; // int * typeof_unqual(pci) pci2u; // int const* typeof (cpi) cpi2; // int *const typeof_unqual(cpi) cpi2u; // int * typeof (cpci) cpci2; // int const *const typeof_unqual(cpci) cpci2u; // int const * 
Enter fullscreen mode Exit fullscreen mode

Conclusion

typeof is complementary to auto as an addition to C for writing type-agnostic code or simplifying complicated declarations.

Top comments (0)