Template Aliases
A parameterized type can provide an alias for types related to its template arguments, so related names are derived rather than written by hand.
Why Generic Code Repeats Types
When you write generic code, you often need to refer to types that are closely tied to the types you are already working with. A template argument might be the type of an element, and a related type might be the type of a key, a pair of elements, or a container's internal value. Writing those related types out by hand every time makes code longer and harder to change. The idea this article covers is that a parameterized type can provide an alias for types related to its template arguments, so that the related name is defined once and reused.
Imagine a template that takes two type arguments, but in your program you almost always use the same type for one of them. Every time you want a value of that template, you must write out both arguments, even though one of them never changes. Repeating that fixed argument is tedious, and it makes the code harder to read. A template alias is the tool for this situation: it lets you define a new template by fixing some or all of the arguments of an existing one.
What do you think happens?
If a template alias fixes one of the two arguments of an existing two-argument template, how many arguments does the new template still need from the user?
Reveal answer
One
Binding an argument means fixing it to a specific type inside the alias, so the alias no longer asks for that argument. Any argument that is not bound remains a placeholder the user supplies. With one of two arguments bound, exactly one remains open.
What a Template Alias Binds
A template alias defines a new template by binding some or all of the template arguments of an existing template. Binding means that a particular argument is given a fixed value inside the alias, so the person using the alias no longer supplies it.
The key word is binding. The alias is not a copy of the original template and it is not a new kind of value. It is a new name for a template in which some or all of the arguments have already been decided. The alias is a name tied to the original template, not an independent copy of it.
- Pair is the original template with two open arguments.
- NamedPair fixes Key and keeps Value as its own parameter.
- IntPair fixes both arguments, so it names one fully specified type.
- The uses on the last two lines show what the user must supply in each case.
Tracing the Binding Step by Step
Follow the alias NamedPair through the code above. The original Pair takes two arguments, Key and Value. The alias declaration binds Key to std::string. Only Value is left as a template parameter of NamedPair, so NamedPair is itself a template that takes one argument. When you write NamedPair<double>, you supply the one open argument, and the bound argument is already decided. The result is a template with fewer open choices than the original.
Partial Binding and Full Binding
The description allows two cases. In partial binding, only some arguments are bound, so the new template still takes arguments of its own. Partial binding leaves some arguments open, so the user supplies only those when using the alias. In full binding, all arguments are bound, so nothing is left for the user to supply. Binding all arguments produces an alias that needs no template arguments at the point of use. Both cases are still an alias: the new name is defined in terms of the original template with its arguments fixed in place.
| Question | Partial binding | Full binding |
|---|---|---|
| How many arguments are bound? | Some, but not all | All of them |
| Does the alias still take arguments? | Yes, the open ones | No |
| What does the user supply at the point of use? | Only the open arguments | Nothing |
| Example from the code above | NamedPair<double> | IntPair |
Partial and full binding both define a new name from an existing template, but they leave different amounts open.
Reading an Alias Declaration
template <typename Value>
using NamedPair = Pair<std::string, Value>;- 1template <typename Value> declares that NamedPair is a template with one open parameter, named Value here.
- 2using NamedPair = introduces the new name for the template being defined.
- 3Pair<std::string, Value> is the original template with its first argument fixed to std::string and its second argument passed through as Value.
- 4Because Value appears on the right side, whatever the user supplies for NamedPair becomes the second argument of Pair.
The value_type Naming Convention
Standard-library containers follow a shared naming convention. Every standard-library container provides value_type as the name of its value type. This is a naming convention rather than a single shared type. Each container exposes its own value_type, which names whatever element type that container holds. Because the name is the same across containers, code that needs the element type can ask the container for value_type instead of naming the element type directly. This is the second idea the article focuses on: a shared convention lets code be written once and work with any container that uses it.
A Generic Function That Uses value_type
The practical payoff of value_type is that a function can work for every container that follows the convention. The function does not need to be told the element type, because it can look that type up through the container itself. In the following illustrative function, the local variable's type is taken from the container rather than written as int.
- The function is written once and does not name int anywhere.
- Container::
value_typelooks up the element type from the container argument. - The same function body works for any container that provides
value_type.
Mistakes When Writing or Reading Aliases
Common mistake Treating an alias as a new, distinct type
What it looks likeBelieving that IntPair is a different type from Pair<int, int>.
Why it failsAn alias is a name tied to the original template, not an independent copy or a new kind of value.
Do this insteadRead IntPair as another name for Pair<int, int>.
Common mistake Supplying an argument that the alias already binds
What it looks likeWriting NamedPair<std::string, double> when NamedPair was declared with Key already fixed.
Why it failsBound arguments are decided inside the alias, so the user supplies only the open ones.
Do this insteadWrite NamedPair<double>.Common mistake Assuming a full binding still takes arguments
What it looks likeWriting IntPair<int, int> after IntPair has been declared as a fully bound alias.
Why it failsBinding all arguments produces an alias that needs no template arguments at the point of use.
Do this insteadWrite IntPair by itself.
Common mistake Hard-coding the element type in a generic function
What it looks likeWriting int first = c.front(); inside a function meant to work for any container.
Why it failsThe function then works only for containers holding int.
Do this insteadUse the container's value_type to name the element type.
Common mistake Thinking value_type is one shared type across containers
Why it failsvalue_typeis a naming convention. Each container'svalue_typenames the element type that container holds.Do this insteadExpect value_type to match each container's own element type.
Practice: Binding a Triple
Given template <typename A, typename B, typename C> struct Triple, write an alias template that binds B to bool and leaves A and C open. Then state how many arguments a user must supply when writing the alias.
Hints
- Start from the form template <typename A, typename C> using Name = Triple<A, bool, C>;
- Only the arguments that are not bound should remain as parameters of the new alias.
- Count the open parameters to answer the last part.
Key Takeaways
- A template alias defines a new template by binding some or all of the template arguments of an existing template, giving a new name with fewer open choices.
- Partial binding leaves some arguments open for the user to supply, while binding all arguments produces an alias that needs no template arguments at the point of use.
- Standard-library containers provide
value_typeas the name of their own element type, so generic code can ask the container for that type instead of hard-coding it. - An alias is a name tied to the original template, not an independent copy, and its bound arguments must not be supplied again at the point of use.