Skip to content
VLSI Mentor

C++ Part I · Module 21 · Modern C++ Type and Utility Tools

Type Aliases: using and typedef

This module's frozen description calls its contents the tools that let a type say more about itself, and a type alias is the smallest of them — small enough that it is usually taught as a syntax note rather than a design tool. It deserves better treatment, for two reasons that this chapter measures. The first is that an alias is the cheapest readability improvement available anywhere in the language: a declaration naming a nested standard-library template drops from forty-one characters to twenty-three and starts reading in the problem's vocabulary rather than the library's, at literally zero run-time cost and with no behavioural change at all. The second is the limitation, which matters more than the feature. An alias creates a new name and not a new type, so two aliases for the same underlying type are interchangeable everywhere — and the chapter measures three differently-named aliases for a thirty-two-bit integer being assigned to one another with no warning from any flag. That is exactly the defect Chapter 21.7 exists to prevent, and understanding precisely what an alias does not do is what makes the later chapter's argument land. In between, the chapter settles the typedef-versus-using question with the one case that decides it: there is a thing typedef cannot express at all, and the error message says so in six words.

Intermediate17 min readC++Part IFundamentalsModern C++

Part I · Module 21 · Chapter 21.1 · Type Aliases: using and typedef

1. Two Spellings, One Meaning

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef std::uint32_t Addr_td;
using   Addr_us = std::uint32_t;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
is_same<Addr_td, Addr_us>        = 1
is_same<Addr_us, std::uint32_t>  = 1

Both declare a name for an existing type, and the names are the same type — not merely compatible, the same, as std::is_same_v confirms. There is no conversion, no wrapper, and no run-time entity.

The using form reads left-to-right — new name, equals sign, existing type — in the same order as every other declaration in the language. typedef puts the new name where a variable name would go, which is why it reads backwards for a simple type and becomes genuinely hard for a compound one:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef int (*Handler_td)(unsigned, const char*);
using   Handler_us = int (*)(unsigned, const char*);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
is_same<Handler_td, Handler_us>  = 1

In the typedef version the new name is buried in the middle of the type. In the using version everything to the right of the = is the type and everything to the left is the name. For a function-pointer type, an array type, or a pointer-to-member, that difference is the whole readability argument — and §4 gives the argument that is not merely about taste.

2. The Readability Case

This is the reason to reach for an alias at all.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
std::map<std::uint32_t, std::vector<Txn>> raw;
Table<std::vector<Txn>>                   named;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
is_same<raw, named>              = 1  (identical types)
the declaration went from 41 characters to 23 (counted), and the
second reads in the problem's vocabulary rather than the library's.

Forty-one characters to twenty-three, counted rather than estimated. But the character count is the lesser half. The first declaration tells a reader how the data is stored; the second tells them what it is. A reader who meets Table<...> learns the concept once; a reader who meets the raw spelling re-parses the nesting at every occurrence.

The effect compounds in a signature, which is where it matters most:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
void merge(const std::map<std::uint32_t, std::vector<Txn>>& a,
           const std::map<std::uint32_t, std::vector<Txn>>& b,
           std::map<std::uint32_t, std::vector<Txn>>& out);

void merge(const Table<std::vector<Txn>>& a,
           const Table<std::vector<Txn>>& b,
           Table<std::vector<Txn>>& out);

And it gives you a single place to change the storage decision. If Table becomes an unordered_map after Chapter 19.7's decision path is re-run, one line changes rather than every declaration.

3. What the Standard Library Does

Aliases are not a convenience the library leaves to you; they are a required part of every container's interface.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
std::vector<Txn>::value_type is Txn?       1
std::vector<Txn>::size_type is size_t?     1
std::string is basic_string<char>?         1

Every standard container publishes value_type, size_type, difference_type, iterator, const_iterator, reference and more. They exist so that generic code can name those types without knowing the container:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
template <typename C>
typename C::value_type total(const C& c)
{
    typename C::value_type sum{};
    for (const auto& x : c) sum += x;
    return sum;
}

That function works on a vector<int>, a list<double> and a set<long> because each one tells it what its elements are. Chapter 20.2 §2's std::iterator_traits is the same mechanism applied to iterators, and it is how std::distance knows which implementation to pick.

std::string being an alias for std::basic_string<char> is the other pattern: a template parameterised for generality, with an alias for the case everybody actually uses. std::stringstream, std::ostream and the fixed-width integer types in <cstdint> are all this.

4. The One Thing typedef Cannot Do

Here is the case that settles the question, and it is not a style preference.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
template <typename T> using Table = std::map<std::uint32_t, T>;

That is an alias template: a family of aliases parameterised on a type. typedef cannot express it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
template <typename T>
typedef std::map<std::uint32_t, T> Table;     // not legal in any standard
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tdtmpl.cpp:5:36: error: a typedef cannot be a template

One error, six words, and the language is simply not able to do it. Before C++11 the workaround was a class template with a nested typedef:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
template <typename T> struct TableOld { typedef std::map<std::uint32_t, T> type; };
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Table<int>                   -> works, reads directly
typename TableOld<int>::type -> a struct, a nested typedef, and a `typename`
  at every use site
is_same<Table<int>, TableOld<int>::type> = 1

The two name the same type, and the second costs a helper struct, a nested name, and the typename keyword at every single use — because inside a template the compiler cannot tell that TableOld<T>::type is a type rather than a value. That is Chapter 18.6 §7's dependent-name rule, and an alias template removes the need for the keyword entirely.

So the rule is simple: use using. It reads better for everything, and for templates it is the only option. typedef remains in the language for compatibility and in older code you will read.

5. Aliases Cost Nothing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sizeof(Addr_us) = 4 == sizeof(std::uint32_t) = 4
sizeof(Table<int>) = 24 == sizeof(std::map<uint32_t,int>) = 24

An alias is a compile-time name. There is no object, no indirection, no conversion, and nothing in the generated code. The is_same results in §1 are the strong form of this: the compiler does not merely treat the types as equivalent, it has only one type.

That is worth stating because the next chapters introduce tools that do cost something — std::optional adds a flag, std::variant adds a tag, std::any may allocate — and an alias is the one tool in this module that is unambiguously free.

6. The Limitation, Measured

And here is what you are not getting.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
using Addr   = std::uint32_t;
using Length = std::uint32_t;
using Offset = std::uint32_t;

Addr   a = 0x1000;
Length n = 64;
Offset o = 8;
a = n; n = o; o = a;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
is_same<Addr_us, Length> = 1   <- DIFFERENT NAMES, SAME TYPE
is_same<Length, Offset>  = 1
a = n; n = o; o = a;  all compile -- a=64 n=8 o=64

Every assignment compiles. There is no warning at -Wall -Wextra -Wpedantic. An address was assigned from a length, a length from an offset, and an offset from an address, and the program is well-formed.

This matters because the alias looked like a safety feature. Writing void read(Addr base, Length n) reads as though the compiler will keep the two apart, and it will not: read(n, a) with the arguments swapped compiles exactly as readily as the correct call. Chapter 21.7 §1 measures that specific defect and the fix.

So the honest summary of what an alias gives you: a better name, a single point of change, and nothing else. It documents intent to the reader and communicates nothing to the compiler. That is a genuine and worthwhile benefit, and it is not type safety.

7. Where Aliases Earn Their Place

Six patterns that are worth the line:

A nested standard-library type, as in §2. The most common and the highest-value case.

A function-pointer or callback type, where the using spelling is dramatically clearer:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
using RegisterHandler = int (*)(std::uint32_t addr, std::uint32_t value);

A domain vocabulary, so signatures read in the problem's terms — accepting §6's caveat that this is documentation:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
using Cycles  = std::int64_t;
using FieldId = std::uint16_t;

A dependent type inside a template, which is where the alias template removes typename noise:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
template <typename C> using IterOf = typename C::iterator;

A partially-bound template, which is the thing alias templates do that nothing else can:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
template <typename V> using StringMap = std::map<std::string, V>;
template <typename T> using Owned     = std::unique_ptr<T, Deleter>;

Your own type's published vocabulary, mirroring §3 so that generic code can use your type:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class Trace {
public:
    using value_type = Txn;
    using size_type  = std::size_t;
    using iterator   = std::vector<Txn>::iterator;
};

8. Where They Do Not

When the raw type is already clear. using Int = int; adds a name and a lookup for a reader and buys nothing.

When you wanted type safety. §6 is the measurement; Chapter 21.7 is the answer.

When the alias hides something a reader needs. using Handle = void*; makes the signature shorter and the semantics less visible, which is the wrong direction — and Chapter 18.1 §3 measured what void* costs besides clarity.

As a substitute for auto. A local variable's type is usually better left to deduction than named through an alias.

Too many of them. An alias per concept is clarity; an alias per declaration is a second vocabulary a reader has to learn before they can read the first.

9. The Comparison

typedefusing
simple typetypedef uint32_t Addr;using Addr = uint32_t;
reads left to rightnoyes
function pointername buried mid-typetype entirely on the right
can be templatedno (measured: 1 error)yes
needs a helper struct for a familyyes, plus typename at useno
creates a new typeno (measured: is_same = 1)no (same)
run-time costnonenone (measured)
available sinceCC++11

10. An Alias Improves the Diagnostic Too

A reasonable worry about aliases is that they hide the real type when something goes wrong — that an error message naming Table is less useful than one naming the std::map it stands for. Measured, that worry is unfounded: clang prints both.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
using Table = std::map<std::uint32_t, std::vector<Txn>>;
using Addr  = std::uint32_t;
static void takeTable(const Table&) {}
static void takeAddr(Addr) {}

std::vector<int> v;
takeTable(v);                 // wrong type
takeAddr("not a number");     // wrong type
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
note: candidate function not viable: no known conversion from 'std::vector<int>'
      to 'const Table' (aka 'const map<unsigned int, vector<Txn>>') for 1st argument

note: candidate function not viable: no known conversion from 'const char[13]'
      to 'Addr' (aka 'unsigned int') for 1st argument

The aka form gives you the alias and its expansion. Your vocabulary appears first, which is what you want when the name is meaningful, and the underlying type follows in parentheses for when it is not — so the reader gets the concept and the mechanics in one line.

This is worth knowing because it removes the main practical objection to naming types. It also previews a difference that Chapter 21.7 turns into its central argument. An alias has to be expanded in the diagnostic, because there is only one type; a strong type does not, because the types are genuinely distinct:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
note: candidate function not viable: no known conversion from 'Id<LenTag>'
      to 'Id<AddrTag>' for 1st argument

No aka, because there is nothing to expand — Id<LenTag> and Id<AddrTag> are two types, and the message says so directly. Compare that with §6's measurement, where the same mistake made with aliases produced no diagnostic at all. The alias improved the error message for a mistake the compiler could already see; the strong type created an error message where there had been none.

11. What to Actually Do

  • Use using, not typedef. Better for everything, and the only option for templates.
  • Alias a nested library type whenever it appears more than once, especially in a signature.
  • Alias function-pointer and callback types — this is where the spelling difference is largest.
  • Publish value_type / size_type / iterator on your own containers, so generic code can use them.
  • Use an alias template to bind a template partially — StringMap<V>, Owned<T>.
  • Use an alias template to remove typename noise from dependent names.
  • Do not alias a type that is already clear. using Int = int; is a cost.
  • Do not expect type safety. Measured: three aliases for uint32_t interassigned with no diagnostic. Chapter 21.7 is the tool for that.
  • Keep the vocabulary small. One alias per concept, not one per declaration.

12. Mistakes Worth Making Once

Expecting an alias to prevent a mixed-up argument. Measured: it does not, at any warning level.

Reaching for typedef inside a template and discovering it cannot be parameterised. One error, and the message is unambiguous.

Writing typename TableOld<T>::type throughout a codebase that could have had an alias template.

using Handle = void*; which shortens the signature and hides the semantics.

Aliasing a pointer type and then adding const. using P = int*; const P p; makes p a const pointer, not a pointer to const — the const applies to the alias as a whole. Chapter 8.7 §2's reading rule applies, and the alias moves where the reader has to apply it.

Forgetting that using also means something else. using namespace std; and using Base::method; are unrelated features that share the keyword.

13. Interview Relevance

The frozen curriculum marks this chapter Medium, which is accurate — it is asked briefly and mostly to check that you know the one real difference.

"What is the difference between typedef and using?" The reading order, and the fact that using can be templated and typedef cannot. Quoting the error — "a typedef cannot be a template" — makes the answer concrete.

"Does a type alias create a new type?" No. The strong answer adds the consequence: two aliases for the same type are interchangeable, so an alias documents intent and enforces nothing, and names the tool that does enforce it.

"What is an alias template?" A parameterised family of aliases; the thing typedef cannot express. Giving template <typename V> using StringMap = std::map<std::string, V>; as the example is enough.

"Why does every standard container define value_type?" So generic code can name the element type without knowing the container — and iterator_traits is the same idea for iterators.

"What does using P = int*; const P p; declare?" A const pointer to non-const int. It is a good question because the alias moves where the const binds.

14. What You Should Be Able to Say Now

  • That both forms name the same type, confirmed by is_same, with no run-time entity.
  • That using reads left to right and is dramatically clearer for compound types.
  • That typedef cannot be templated, and the error is one line.
  • That the pre-C++11 workaround costs a helper struct plus typename at every use.
  • That a declaration went from 41 characters to 23, and that the vocabulary change matters more.
  • That the standard library publishes aliases as part of every container's interface.
  • That an alias creates a name and not a type — measured, three aliases for uint32_t interassigned silently.
  • That Chapter 21.7 is the tool for the enforcement an alias does not provide.

15. Exercises

Exercise 1 — the two spellings. Write typedef and using declarations for: a pointer to a function returning int and taking two doubles; an array of ten uint32_t; and a pointer to a member function of a class. Compare the readability.

Exercise 2 — the template case. Try to write an alias template with typedef, read the error, then implement the pre-C++11 workaround and count the typename keywords at the use sites.

Exercise 3 — the limitation. Reproduce §6. Then add -Weverything and confirm nothing fires. Write the two-line note you would leave for a reader explaining what the aliases do and do not guarantee.

Exercise 4 — the vocabulary. Take a signature from your own code with three or more nested library types and rewrite it with aliases. Count characters before and after, and ask a colleague which reads better.

Exercise 5 — the const trap. Work out what using P = int*; const P p = &x; declares, verify with is_same, and then write the version that declares a pointer to const int.

Exercise 6 — publishing aliases. Add value_type, size_type, iterator and const_iterator to a small container of your own, then write a generic total() that works on it and on a std::vector.

Exercise 7 — the partial bind. Write alias templates for a map keyed by string, a vector with a custom allocator, and a unique_ptr with a custom deleter. Show each in use.

16. Summary

  • An alias names an existing type. using and typedef produce the same type — measured, is_same = 1.
  • using reads left to right; typedef buries the new name inside compound types.
  • Measured: typedef cannot be templated — "a typedef cannot be a template", one error — and the pre-C++11 workaround costs a struct, a nested name and typename at every use.
  • Measured: 41 characters to 23, and the alias reads in the problem's vocabulary rather than the library's.
  • Measured: zero cost. sizeof and is_same confirm there is no new entity.
  • The standard library publishes aliases as interface — value_type, size_type, iterator, and std::string for basic_string<char>.
  • Measured: an alias creates a NAME, not a TYPE. is_same<Addr, Length> = 1, and a = n; n = o; o = a; compiled with no diagnostic at any warning level.
  • So an alias documents intent and enforces nothing — a real benefit, but not type safety.
  • Use using. Alias nested types, callbacks, domain vocabulary and partially-bound templates; do not alias what is already clear.

The next chapter takes the other kind of compile-time tool: not naming a type, but performing a computation. Chapter 21.2 is constexpr, and it is the one place in this module where the curriculum's own frozen example turns out to contain a latent bug — a register-mask helper of exactly the shape found in real hardware code, which returns a silently wrong answer at one input and which constexpr evaluation refuses to compile, naming the cause precisely.

Standards & specifications

Governing standard
ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)

Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C++ Programming curriculum.