I’m sure typename and typedef are not new to anyone who has used C++, but I still couldn’t understand the following code when I saw it.

1
typedef typename std::vector<T>::size_type size_type;

It stands to reason that typedef is not generally used to define an alias for a type, as follows.

1
typedef int SpeedType;

Having defined an int with the alias SpeedType, I can then use it like this.

1
2
3
4
5
6
int main(void)
{
    SpeedType s = 10;
    printf("speed is %d m/s",s);
    return 0;
}

But what does typedef followed by typename mean, and isn’t typename used to define template parameters? Let’s summarize the usage of typedef & typename separately.

typedef

First, let’s look at a few common uses of typedef.

Aliases for types with specific meanings

I’ve already covered this above, but it’s defining an alias for a type, not just a simple macro substitution, and can also be used to declare multiple objects of the pointer type at the same time.

For example.

1
2
3
char* pa, pb;  
cout << typeid(pa).name() << endl; //Pc
cout << typeid(pb).name() << endl; //c

We wanted to declare both pa and pb as strings, but we could only declare one of them successfully.

But we can declare both of them using typedef.

1
2
3
4
typedef char* PCHAR; 
PCHAR pa, pb; // Workable
cout << typeid(pa).name() << endl; //Pc
cout << typeid(pb).name() << endl; //Pc

Aliasing structs

When declaring variables, you need to bring struct, i.e. use it like the following.

1
2
3
4
5
6
7
typedef struct info
{
    char name[128];
    int length;
}Info;

Info var;

Used to define platform-independent types

For example, define a floating point type called REAL, on target platform 1, and let it represent the highest precision type as follows.

1
typedef long double REAL;

On platform 2, which does not support long double, it is modified as follows.

1
typedef double REAL;

When cross-platform, just change the typedef itself, without making any changes to the rest of the source code.

typename

The typename keyword is used to introduce a template parameter. This keyword is used to indicate that the non-dependent names in the template declaration (or definition) are type names, not variable names.

1
2
3
4
5
6
7
8
template <typename T>
const T& max(const T& x, const T& y)
{
  if (y < x) {
    return x;
  }
  return y;
}

typename in this context means that T is a type. Without it, ambiguity can arise in some cases, such as the following.

1
2
3
4
5
6
template <class T>
void foo() {
    T::iterator * iter;
    // ...

}

The author wants to define a pointer iter that points to an iterator of type contained in the class scope T. It is possible that there is such a structure containing the type iterator.

1
2
3
struct ContainsAType {
    struct iterator { /*...*/ }; 
};

Then foo<ContainsAType>(); when used this way does tell iter is a pointer of type ContainsAType::iterator. But T::iterator can actually be of any of the following three types.

  • Static data members
  • Static member functions
  • Nested types

So if it is a case like the following.

1
2
3
4
struct ContainsAnotherType {
    static int iterator;
    // ... 
};

Then T::iterator * iter; is instantiated by the compiler as ContainsAnotherType::iterator * iter; and becomes a static data member multiplied by iter, so that the compiler will not find the definition of another variable iter. So to avoid this ambiguity, we add typename to indicate that T::iterator must be a type.

1
2
3
4
5
template <class T>
void foo() {
    typename T::iterator * iter;
    // ... 
}

Drawing conclusions

Let’s go back to the example at the beginning, for vector::size_type, we can know that

1
2
3
4
5
6
7
template <class T,class Alloc=alloc>
class vector{
public:
    //...
    typedef size_t size_type;
    //...
};

vector::size_type is a nested type definition of vector, which is actually equivalent to the size_t type.

1
typedef typename std::vector<T>::size_type size_type;

The real face of this example, then, is that typedef creates an alias for the existent type, and typename tells the compiler that std::vector<T>::size_type is a type and not a member.

An example

Well, after reading the above example you should have fully understood the essence of typedef & typename, let’s explain an example of using a template to implement a loop similar to the one below.

1
2
3
4
5
int result = 0;
while (n != 0) {
  result = result + n;
  n = n - 1;
}

First we need a loop template.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
template <bool condition,typename Body>
struct WhileLoop;

template <typename Body>
struct WhileLoop<true, Body> {
    typedef typename WhileLoop<Body::cond_value, typename Body::next_type>::type type;
};

template <typename Body>
struct WhileLoop<false, Body> {
    typedef typename Body::res_type type;
};

template <typename Body>
struct While {
    typedef typename WhileLoop< Body::cond_value, Body>::type  type;
};

As you can see here, the templates, both res_type and type, are modified with typename to indicate that they are types, and then typedef to indicate that an alias is defined for the type.

When defining the loop template again, there is a convention that it must provide a static data member, cond_value, and two subtype definitions, res_type and next_type.

  • cond_value represents the condition of the loop (true or false), indicating that it is directly a static data member of definite bool type, unmodified by typename.
  • res_type represents the state when the loop is exited, and is a type rather than a member.
  • next_type represents the state when the following loop is executed once, and is a type rather than a member.

WhileLoop uses specialization to decide whether to take the recursive branch or exit the loop branch.

We then define a template to represent the value.

1
2
3
4
5
6
template <class T, T v>
struct integral_constant {
    static const T value = v;
    typedef T value_type;
    typedef integral_constant type;
};

The value can be obtained by value, and the value_type is the type of the value.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template <int result, int n>
struct SumLoop {
    static const bool cond_value =
        n != 0;
    static const int res_value =
        result;
    typedef integral_constant< int, res_value>  res_type;
    typedef SumLoop<result + n, n - 1>  next_type;
};

template <int n>
struct Sum {
    typedef SumLoop<0, n> type;
};

The above template enables the result of While<Sum<10>::type>::type::value 1 to 10. In fact, the result of the 1 to 10 operation is achieved by a circular expansion of types.

Reference

  • https://www.luozhiyun.com/archives/742