typeof in C23
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
Note: for this article, I’ll simply use
typeof
to mean eithertypeof
ortypeof_unqual
rather than always repeating “ortypeof_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 likedecltype
in C++. Why isn’ttypeof
calleddecltype
in C also? (Or why isn’tdecltype
in C++ calledtypeof
?) 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
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()
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
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 ofsizeof
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]
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 *
Conclusion
typeof
is complementary to auto
as an addition to C for writing type-agnostic code or simplifying complicated declarations.