GotW #46

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original GotW problem and solution substantially as posted to Usenet. See the book More Exceptional C++ (Addison-Wesley, 2002) for the most current solution to this GotW issue. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard.

Typedefs
Difficulty: 3 / 10

Why use typedef? Besides the many traditional reasons, we'll consider typedef techniques that make using the C++ standard library safer and easier.

Problem

JG Question

1. What does typedef do?

Guru Questions

2. Why use typedef? Name as many situations/reasons as you can.

3. Why is typedef such a good idea in code that uses standard (STL) containers?

Solution

1. What does typedef do?

Writing "typedef" allows you to assign another equivalent name for a type. For example:

    typedef vector< vector<int> > IntArray;

lets you write the simpler "IntArray" in place of the more verbose "vector< vector<int> >".

2. Why use typedef? Name as many situations/reasons as you can.

Here are several "abilities":

Typeability

Shorter names are easier to type.

Readability

Typedefs can make code, especially long template type names, much more readable. For a simple example, consider the following from a public newsgroup posting asking what this code meant:

    int ( *t(int) )( int* );

If you're used to reading C declarations just like you'd read a Victorian English novel (i.e., dense and verbose prose that at times feels like slogging through ankle-deep sucking muck), you know the answer and you're fine. If you're not, typedefs really help, even with as meaningless a typedef name as "Func":

    typedef int (*Func)( int* );
    Func t( int );

Now it's clearer that this is a function declaration, for a function named "t" that takes an int and returns a pointer to a function that takes an int* and returns an int. (Say that three times fast.) In this case, the typedef is easier to read than the English.

Typedefs can also add semantic meaning. For example, "PhoneBook" is much easier to understand than "map< string, string>" (which could mean anything!).

Portability

If you use typedef'd names for platform-specific or otherwise nonportable names, you'll find it easier to move to new platforms. After all, it's easier to write this:

    #if defined USING_COMPILER_A
      typedef __int32 Int32;
      typedef __int64 Int64;
    #elif defined USING_COMPILER_B
      typedef int       Int32;
      typedef long long Int64;
    #endif

than search-and-replace for one of the system-specific names throughout your code. The typedef names insulate you from simple platform dependencies.

3. Why is typedef such a good idea in code that uses standard (STL) containers?

Flexibility

Changing a typedef name in one place is easier than changing all of its uses throughout the code. For example, consider the following code:

    void f( vector<Customer>& vc ) {
      vector<Customer>::iterator i = vc.begin();
      ...
    }

What if a few months later you find that vector isn't the right container? If you're storing huge numbers of Customer objects, the fact that vector's storage is contiguous[1] may be a disadvantage and you'd like to switch to deque instead. Or, if you're frequently inserting/removing elements from the middle, you'd like to switch to list instead.

In the above code, you'd have to make that change everywhere "vector<Customer>" appears. How much easier it would be if you had only written:

    typedef vector<Customer> Customers;

    ...

    void f( Customers& vc ) {
      Customers::iterator i = vc.begin();
      ...
    }

and only needed to change the typedef to list<Customer> or deque<Customer>! It's not always this easy -- for example, your code might be relying on Customers::iterator being a random-access iterator, which a list<Customer::iterator isn't -- but this does insulate you from a lot of otherwise-tedious changes.

Traitability

The traits idiom is a powerful way to associate information with a type, and if you want to customize standard containers or algorithms you'll often need to provide traits. Consider the case-insensitive string example from GotW #29, where we defined our own char_traits replacement.

Most uses of typedef fall into one of these categories.

 

Notes

1. Yes, I'm aware of the debate, and yes, it should be contiguous. (Later note: See also Standard Library News, Part 1: Vectors and Deques.)

Copyright 2009 Herb Sutter