Type aliases and alias templates

A type alias gives a type a shorter name. using table = std::map<std::string, std::vector<int>>; declares one, and every later use of table is the same type, not a new one. The older typedef does the same with a syntax that reads backwards.

A template alias goes one step further and leaves a parameter open: template <typename T> using Vec = std::vector<T>;. This lesson covers both, and the typename keyword that a function template needs when it names a type nested inside another template.

A name for a type

The alias below names a map from strings to vectors of ints. The declaration is a compile-time name only: table and the long type are the same type, and the compiler treats them as identical.

The program fills one entry and prints its size. The alias makes the declaration readable and keeps the type in one place, so a change from vector<int> to vector<double> is one line.

A using alias can appear at namespace scope, in a class or in a function. The typedef form is still valid and still found in older code; the using form is preferred because it reads left to right and it can be templated.

An alias is not a new type. Overloads cannot tell table from the map it names, and a template that matches std::map matches the alias too; that is the point, and it is also why an alias cannot be used to add behaviour.

#include <iostream> #include <map> #include <string> #include <vector> using table = std::map<std::string, std::vector<int>>; int main() { table scores; scores["ada"] = {1, 2, 3}; std::cout << scores["ada"].size() << '\n'; }

g++ -std=c++23 -Wall -Wextra using.cpp -o using_alias
./using_alias

3

An alias template

An alias template has a template parameter list in front of the using. template <typename T> using Vec = std::vector<T>; makes Vec<int> another name for std::vector<int> and leaves T open for the user.

The program declares a Vec<int> and a Vec<std::string> and prints their elements. The alias does not create a new type, so a function that takes std::vector<int> accepts a Vec<int> without a conversion.

Alias templates are how the standard library spells std::remove_reference_t and std::enable_if_t: each is an alias template with a _t suffix, and the underlying class template does the work.

An alias template cannot be partially specialised and cannot deduce its own arguments, so it is a name rather than a new class. When a library needs to change behaviour per type, it uses a class template such as lesson 039.

#include <iostream> #include <string> #include <vector> template <typename T> using Vec = std::vector<T>; int main() { Vec<int> numbers{1, 2, 3}; Vec<std::string> words{"one", "two"}; std::cout << numbers.size() << ' ' << words[0] << '\n'; }

g++ -std=c++23 -Wall -Wextra alias_template.cpp -o alias_template
./alias_template

3 one

A dependent type needs typename

A class template can publish a nested type, as the standard containers publish value_type. Inside another template, Box<T>::value_type depends on T, and the compiler cannot know it is a type until T is known.

The keyword typename tells the compiler that the name is a type, so the declaration parses. The function below declares a local variable of type typename Box<T>::value_type, and the call with Box<int> prints an int.

C++20 made the keyword optional in some places, including return types and parameter declarations, so a function that returns the nested type may compile without it. A local declaration inside a function body still needs it, which is the case below.

Every _t alias in the standard library exists partly to avoid this syntax in user code: std::remove_reference_t<T> is shorter and needs no typename at the use site.

#include <iostream> template <typename T> class Box { public: using value_type = T; explicit Box(T value) : value_(value) {} value_type get() const { return value_; } private: T value_; }; template <typename T> void show(const Box<T>& box) { typename Box<T>::value_type value = box.get(); std::cout << value << '\n'; } int main() { Box<int> box{7}; show(box); }

g++ -std=c++23 -Wall -Wextra typename.cpp -o typename_demo
./typename_demo

7

Without typename

The same declaration without the keyword is rejected: the compiler reads Box<T>::value_type as a value, and a declaration cannot start with a value. The note asks for typename before the name.

The message also explains that Box<T> is a dependent scope, which is why the compiler could not look the name up earlier. The fix is one keyword in front of the type.

The error only appears in a template. Outside one, Box<int>::value_type is known at parse time and no keyword is needed, which is why the rule is easy to forget between two similar-looking functions.

#include <iostream> template <typename T> class Box { public: using value_type = T; explicit Box(T value) : value_(value) {} value_type get() const { return value_; } private: T value_; }; template <typename T> void show(const Box<T>& box) { Box<T>::value_type value = box.get(); std::cout << value << '\n'; } int main() { Box<int> box{7}; show(box); }

g++ -std=c++23 -Wall -Wextra no_typename.cpp -o no_typename

no_typename.cpp: In function 'void show(const Box<T>&)':
no_typename.cpp:23:5: error: need 'typename' before 'Box<T>::value_type' because 'Box<T>' is a dependent scope [-Wtemplate-body]
23 | Box<T>::value_type value = box.get();
| ^~~~~~
no_typename.cpp:24:18: error: 'value' was not declared in this scope [-Wtemplate-body]
24 | std::cout << value << '\n';
| ^~~~~
no_typename.cpp: In instantiation of 'void show(const Box<T>&) [with T = int]':
no_typename.cpp:30:9: required from here
30 | show(box);
| ~~~~^~~~~
no_typename.cpp:21:25: warning: unused parameter 'box' [-Wunused-parameter]
21 | void show(const Box<T>& box)
| ~~~~~~~~~~~~~~^~~

Exercises

  1. Add an alias for std::map to using.cpp and use it for a second table.
  2. Write an alias template that adds a const to its type: `template using const_ref = const T&;` and use it in a function parameter.
  3. Add typename to no_typename.cpp and check that it compiles, then remove the keyword from a non-template function and see whether the error returns.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What is a type alias?

    Answer

    Another name for an existing type

    The alias is the same type under a shorter or clearer name, not a distinct type.

  2. How does using differ from typedef?

    Answer

    The meaning is the same, and the using form reads left to right

    Both declare an alias, and the modern syntax is easier to read for complex types.

  3. What does an alias template leave open?

    Answer

    A template parameter, so the alias is instantiated per type

    template <typename T> using Vec = std::vector<T>; is a pattern for aliases, not one alias.

  4. When is typename required before a dependent type?

    Answer

    When the type is a member of a template parameter, such as Box<T>::value_type

    The compiler cannot know that the name is a type until T is known, and the keyword says so.

  5. What does this program print? #include <iostream> #include <map> #include <string> #include <vector> using table = std::map<std::string, std::vector<int>>; int main() { table values; values["a"] = {1, 2, 3}; std::cout << values["a"].size(); }

    Answer

    3

    The alias names the same map type, and the program stores three values under one key.

  6. What does this program print? #include <iostream> #include <string> #include <vector> template <typename T> using vec = std::vector<T>; int main() { vec<int> numbers{1, 2, 3}; vec<std::string> words{"one"}; std::cout << numbers.size() << ' ' << words[0]; }

    Answer

    3 one

    The alias template is instantiated for int and string, and the program prints a size and a value.

  7. What does this program print? #include <iostream> template <typename T> struct box { using value_type = T; T value; }; template <typename T> T read(const box<T>& b) { typename box<T>::value_type copy = b.value; return copy; } int main() { box<int> b{7}; std::cout << read(b); }

    Answer

    7

    The dependent type needs typename, and the function returns the value it copied.

  8. What does omitting typename report?

    Answer

    need 'typename' before 'Box<T>::value_type' because 'Box<T>' is a dependent scope

    The compiler parses the member as a value, and the keyword is what makes it a type.

  9. Why prefer an alias template to a new wrapper class?

    Answer

    The alias is the same type, so no conversion or wrapper is needed

    A wrapper changes the type and the interface; an alias only changes the spelling.

  10. What does a dependent name depend on?

    Answer

    A template parameter

    The name cannot be resolved until the template arguments are known, which is why the parser needs help.

The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.

Article author: Arthur Isaev