What Is an Alias Template?

An alias template is a name for a family of types rather than for one type. It is written like an ordinary type alias but with a template parameter list in front of it, so the alias itself takes template arguments and expands to the type you built out of them.

The distinction is easiest to see against the alias you already know. using gives one name to one type. An alias template gives one name to a pattern, and you fill in the blank each time you use it.

Naming a Single Instantiation

Start with a class template and no aliasing at all. Interval<int> is a perfectly good type name, but it is a name the compiler cares about more than a reader does. If a particular instantiation means something specific in your program, an ordinary type alias can say so:

#include <iostream>

template <typename T>
struct Interval
{
    T lower{};
    T upper{};
};

int main()
{
    using DayRange = Interval<int>; // an ordinary type alias

    DayRange sprint{ 12, 26 };

    std::cout << "days " << sprint.lower << " to " << sprint.upper << '\n';

    return 0;
}
days 12 to 26

Nothing new is happening here. Every template argument is supplied inside the alias, so DayRange names exactly one type and behaves like any other alias: it can be declared at namespace scope or, as above, inside a function.

The limitation shows up the moment you want the same convenience for more than one element type. using DayRange = Interval<int>; says nothing about Interval<double>, and writing one alias per element type does not scale.

Leaving the Parameter Open

Put a template parameter list in front of the alias and the argument becomes something the user of the alias supplies:

#include <iostream>

template <typename T>
struct Interval
{
    T lower{};
    T upper{};
};

template <typename T>
using Bounds = Interval<T>;

int main()
{
    Bounds<int> sprint{ 12, 26 };
    Bounds<double> voltage{ 3.15, 3.45 };

    std::cout << "days " << sprint.lower << " to " << sprint.upper << '\n';
    std::cout << "volts " << voltage.lower << " to " << voltage.upper << '\n';

    return 0;
}
days 12 to 26
volts 3.15 to 3.45

Bounds is the alias template. Bounds<int> and Bounds<double> are the type aliases it produces. The general shape is:

template <typename T>
using AliasName = SomeType<T>;

The template parameter list opens the declaration, followed by using, the alias name, =, and the type being named. Note that there is no <T> after the alias name on the left. The parameters are declared once, in the parameter list, and used on the right.

The Alias Is Not a New Type

An alias template does exactly as much as a plain alias does, which is to say it introduces a spelling and nothing else. Bounds<int> and Interval<int> are one type with two names, so a function written against either signature accepts both:

#include <iostream>

template <typename T>
struct Interval
{
    T lower{};
    T upper{};
};

template <typename T>
using Bounds = Interval<T>;

int width(const Interval<int>& span)
{
    return span.upper - span.lower;
}

int main()
{
    Bounds<int> sprint{ 12, 26 };
    Interval<int> quarter{ 1, 13 };

    std::cout << width(sprint) << '\n';
    std::cout << width(quarter) << '\n';

    return 0;
}
14
12

width was declared to take an Interval<int> and it takes a Bounds<int> without a conversion, because there is nothing to convert.

Key Concept
An alias template never introduces a distinct type, so it cannot be overloaded on, specialized, or used to make the compiler treat two things as different. If you need a genuinely separate type, write a separate class template.

Pinning Some Arguments and Leaving Others Open

The previous example passed its parameter straight through, which is the simplest case and not the most useful one. An alias template can fix some of the underlying template's arguments while leaving the rest open, which is how you turn an unwieldy multi-parameter template into the two or three shapes your program actually uses:

#include <iostream>
#include <string_view>

template <typename Key, typename Value>
struct Entry
{
    Key label{};
    Value amount{};
};

template <typename Value>
using NamedEntry = Entry<std::string_view, Value>;

int main()
{
    NamedEntry<int> stock{ "bolts", 480 };
    NamedEntry<double> mass{ "resin", 2.75 };

    std::cout << stock.label << ' ' << stock.amount << '\n';
    std::cout << mass.label << ' ' << mass.amount << '\n';

    return 0;
}
bolts 480
resin 2.75

NamedEntry takes one argument even though Entry takes two. The key type is baked into the alias, and the caller only chooses what varies. The number of parameters an alias template takes has nothing to do with the number the aliased template takes.

The Right-Hand Side Need Not Be a Class Template

Anything that is a type can sit on the right of the =, including compound types built out of the parameter. A pointer alias is the classic example:

#include <iostream>

template <typename T>
using Handle = T*;

int main()
{
    int reading{ 57 };
    Handle<int> probe{ &reading };

    *probe = 61;

    std::cout << reading << '\n';

    return 0;
}
61

Handle<int> is simply int*, and probe is an ordinary pointer in every respect.

Alias Templates Belong at Namespace Scope

An alias template is a template, and templates cannot be declared inside a function body. This program does not compile:

#include <iostream>

template <typename T>
struct Interval
{
    T lower{};
    T upper{};
};

int main()
{
    template <typename T>
    using Bounds = Interval<T>; // not allowed inside a function

    Bounds<int> sprint{ 12, 26 };

    std::cout << sprint.lower << '\n';

    return 0;
}

GCC rejects it with error: a template declaration cannot appear at block scope. Move the alias template out to namespace scope, above main, and it compiles. This is one of the few practical differences between an ordinary type alias, which is happy inside a block, and an alias template, which is not.

Function Parameters Need the Argument List Spelled Out

When an alias template appears in a function parameter, you must write the argument list. Leaving it off is an error rather than a request for deduction, so the following does not compile:

#include <iostream>

template <typename T>
struct Interval
{
    T lower{};
    T upper{};
};

template <typename T>
using Bounds = Interval<T>;

void report(const Bounds& span) // missing the template argument
{
    std::cout << span.lower << " to " << span.upper << '\n';
}

int main()
{
    Bounds<int> sprint{ 12, 26 };

    report(sprint);

    return 0;
}

The compiler answers with error: missing template argument list after 'Bounds'; template placeholder not permitted in parameter. The fix is to make the function a template of its own and write Bounds<T>:

template <typename T>
void report(const Bounds<T>& span)
{
    std::cout << span.lower << " to " << span.upper << '\n';
}

This is not a rule about aliases. Writing const Interval& as a parameter type would fail for the same reason. The alias inherits the behavior of the thing it names.

Deducing the Argument in C++20

Since C++20, an alias template can have its arguments deduced from an initializer wherever class template argument deduction would work on the underlying type. This is called alias template deduction:

#include <iostream>

template <typename T>
struct Interval
{
    T lower{};
    T upper{};
};

template <typename T>
using Bounds = Interval<T>;

template <typename T>
void report(const Bounds<T>& span)
{
    std::cout << span.lower << " to " << span.upper << '\n';
}

int main()
{
    Bounds<int> sprint{ 12, 26 };
    Bounds voltage{ 3.15, 3.45 }; // C++20 alias template deduction

    report(sprint);
    report(voltage);

    return 0;
}
12 to 26
3.15 to 3.45

Bounds voltage{ 3.15, 3.45 }; deduces Bounds<double>, because Interval supports CTAD from those initializers. Before C++20 the argument list was mandatory in this position too, so older code writes Bounds<double> everywhere. Deduction still does not apply to the function parameter in report, since that is a declaration and not an initialization.

Best Practice
Use an alias template when a template instantiation appears often enough that its full spelling gets in the way, or when you find yourself repeating the same fixed argument at every use. Keep the alias name meaningful in your problem domain rather than a shorthand for the underlying template's name.

Summary

Ordinary alias, one type: using DayRange = Interval<int>; supplies every template argument, so it names exactly one type and may be written inside a block or at namespace scope.

Alias template, a family of types: template <typename T> using Bounds = Interval<T>; leaves an argument for the user to supply. Bounds is the alias template; Bounds<int> is the type alias it produces.

Syntax: parameter list, then using, then the alias name, then =, then the type. The parameters are declared on the left and used on the right, never both.

No new type: Bounds<int> and Interval<int> are the same type, so anything written against one accepts the other.

Partial pinning: an alias template may fix some of the underlying template's arguments and leave the rest open, and its own parameter count is independent of the aliased template's.

Any type on the right: the aliased type does not have to be a class template. template <typename T> using Handle = T*; makes Handle<int> mean int*.

Namespace scope only: an alias template cannot be declared inside a function body, unlike an ordinary type alias.

Function parameters: write Bounds<T> and make the function a template. A bare Bounds in a parameter list is an error.

C++20 deduction: alias template deduction fills in the arguments from an initializer wherever CTAD would work on the aliased type.