Concepts / Template Aliases

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.

  • Programming

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?

  • None

  • One

  • Two

Reveal answer
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

Definition

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.

cpp
Notes on the code
  • 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.

Pair<Key, Value>original template, two openargumentsNamedPair<Value>Key bound, Value still openIntPairKey and Value bound,nothing openbind Keybind Key and Value
When an alias binds only some arguments, what is still open, and how does that differ from binding all of them?

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.

QuestionPartial bindingFull binding
How many arguments are bound?Some, but not allAll of them
Does the alias still take arguments?Yes, the open onesNo
What does the user supply at the point of use?Only the open argumentsNothing
Example from the code aboveNamedPair<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

Code walkthrough
template <typename Value>
using NamedPair = Pair<std::string, Value>;
  1. 1
    template <typename Value> declares that NamedPair is a template with one open parameter, named Value here.
  2. 2
    using NamedPair = introduces the new name for the template being defined.
  3. 3
    Pair<std::string, Value> is the original template with its first argument fixed to std::string and its second argument passed through as Value.
  4. 4
    Because 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.

std::vector<int>container with int as itsargumentvalue_typename the container providesintelement type it namesexposesnames
Which type does the container's value_type name, and how is it linked to the container's template argument?

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.

cpp
Notes on the code
  • The function is written once and does not name int anywhere.
  • Container::value_type looks up the element type from the container argument.
  • The same function body works for any container that provides value_type.
Containerstd::vector<int> passed inContainer::value_typeintfirstlocal variable of theelement typelooked upgives its type
How does the element type flow from the container's value_type into the function's local variable when the template is used?

Mistakes When Writing or Reading Aliases

  • Common mistake

    Treating an alias as a new, distinct type

    What it looks like

    Believing that IntPair is a different type from Pair<int, int>.

    Why it fails

    An alias is a name tied to the original template, not an independent copy or a new kind of value.

    Do this instead

    Read IntPair as another name for Pair<int, int>.

  • Common mistake

    Supplying an argument that the alias already binds

    What it looks like

    Writing NamedPair<std::string, double> when NamedPair was declared with Key already fixed.

    Why it fails

    Bound arguments are decided inside the alias, so the user supplies only the open ones.

    Do this instead
    Write NamedPair<double>.
  • Common mistake

    Assuming a full binding still takes arguments

    What it looks like

    Writing IntPair<int, int> after IntPair has been declared as a fully bound alias.

    Why it fails

    Binding all arguments produces an alias that needs no template arguments at the point of use.

    Do this instead

    Write IntPair by itself.

  • Common mistake

    Hard-coding the element type in a generic function

    What it looks like

    Writing int first = c.front(); inside a function meant to work for any container.

    Why it fails

    The function then works only for containers holding int.

    Do this instead

    Use the container's value_type to name the element type.

  • Common mistake

    Thinking value_type is one shared type across containers

    Why it fails

    value_type is a naming convention. Each container's value_type names the element type that container holds.

    Do this instead

    Expect value_type to match each container's own element type.

Practice: Binding a Triple

MEDIUM

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_type as 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.

Related concepts

More concepts starting with T