The typedefs I've shown thus far behave like a #define macro that substitutes a synonym with its actual type. Yet unlike macros, typedefs are interpreted at compile-time, thereby enabling the compiler to cope with textual substitutions that are beyond the preprocessor's capabilities. For example,
typedef int (*PF) (const char *, const char *);
This declaration introduces the type PF as a synonym for 'pointer to function taking two const char * arguments and returning int'. In the following function declaration, the use of this typedef is indispensable:
PF Register(PF pf);
Register() takes a callback function
of type PF and returns the address of a function
with a similar signature that was previously registered. Take a deep breath. I'm about to show you how this declaration would look without a typedef:
int (*Register (int (*pf)(const char *, const char *)))
(const char *, const char *);
Few programmers understand what it means, not to mention its risk of introducing mistakes into such convoluted code. Obviously, the use of a typedef here isn't a prerogative but a must. "OK, but does anyone write such code anyway?" the skeptics among you are probably asking. A quick glance at the <csignal> header file reveals signal()
, a function which has a similar interface.
typedef and Storage Specifiers
Surprising as it may sound, typedef is a storage class specifier, just like auto, extern, mutable, static and register. This doesn't mean that typedef actually affects the storage characteristics of an object; it only means that syntactically, typedef declarations look like declarations of static, extern etc., variables. This brings us to trap #2:
typedef register int FAST_COUNTER; //error
This won't compile. The problem is that you can't have multiple storage class specifiers in a declaration. Because the token typedef already occupies the position of a storage class specifier, you can't use register (or any other storage class specifier) in a typedef declaration.
Facilitating Cross-platform Development
typedefs have another important use, namely defining machine-independent types. For example, you can define a floating point type called REAL that has the highest precision available on the target machine:
typedef long double REAL;
On machines that don't support long double, this typedef will look like this:
typedef double REAL;
And on machines that don't even support double:
typedef float REAL;
You can compile applications that use the type REAL on every platform without making any changes to the source file. The only thing that will change is the typedef itself. In most cases, even this tiny change will be totally automatic thanks to the wonders of conditional compilation
. Nifty, isn't it? The Standard Library uses typedefs extensively to create such platform-independent types: size_t, ptrdiff_t and fpos_t are a few examples. Likewise, typedefs such as std::string and std::ofstream hide the long and nearly unintelligible template specializations basic_string<char, char_traits<char>, allocator<char> > and basic_ofstream<char, char_traits<char> >, respectively.