#109 – When template types get too long, use an alias template

The problem

Suppose we have a generic Reading type:

#include <chrono>
#include <iostream>
#include <string_view>

template <typename Value, typename Timestamp, typename Source>
struct Reading {
    Value value {};
    Timestamp timestamp {};
    Source source {};
};

Reading<double, std::chrono::time_point<std::chrono::system_clock>, std::string_view>
makeVoltageReading(double voltage)
{
    return Reading<double, std::chrono::time_point<std::chrono::system_clock>, std::string_view> {
        .value = voltage,
        .timestamp = std::chrono::system_clock::now(),
        .source = "battery monitor"
    };
}

void printReading(
    const Reading<double, std::chrono::time_point<std::chrono::system_clock>, std::string_view>& reading){
    std::cout << reading.source << ": "
              << reading.value << " V\\n";
}

int main(){
    auto reading { makeVoltageReading(12.4) };

    printReading(reading);
}

Terminal output:
battery monitor: 12.4 V

The repeated type is the problem:

Reading<double, std::chrono::time_point<std::chrono::system_clock>, std::string_view>

It is technically correct, but it is very long.

Creating an alias template

An alias template looks like this:

template <typename Value>
using SensorReading =
    Reading<Value, std::chrono::time_point<std::chrono::system_clock>, std::string_view>;

Value remains flexible.

The timestamp type and source type are fixed:

std::chrono::time_point<std::chrono::system_clock>
std::string_view

Now the code can say:

SensorReading<double>
SensorReading<int>
SensorReading<float>

instead of writing the full Reading<...> type every time.

NOTE: Alias templates do not support CTAD

Refactoring to use an alias template

Here is the refactored program:

#include <chrono>
#include <iostream>
#include <string_view>

template <typename Value, typename Timestamp, typename Source>
struct Reading {
    Value value {};
    Timestamp timestamp {};
    Source source {};
};

template <typename Value>
using SensorReading =
    Reading<Value, std::chrono::time_point<std::chrono::system_clock>, std::string_view>;

SensorReading<double> makeVoltageReading(double voltage){
    return SensorReading<double> {
        .value = voltage,
        .timestamp = std::chrono::system_clock::now(),
        .source = "battery monitor"
    };
}

void printReading(const SensorReading<double>& reading){
    std::cout << reading.source << ": "
              << reading.value << " V\\n";
}

int main(){
    auto reading { makeVoltageReading(12.4) };

    printReading(reading);
}

Terminal output:
battery monitor: 12.4 V

The main improvement is not that the code became shorter. The improvement is that the type now has a domain name:

SensorReading<double>

That tells the reader what the type represents.

The underlying implementation is still:

Reading<double, std::chrono::time_point<std::chrono::system_clock>, std::string_view>

The alias hides the repetitive machinery and exposes the concept.

Alias templates do not create new types

An alias template does not define a new type.

It creates another name for an existing type pattern.

This means:

SensorReading<double>

is the same type as:

Reading<double, std::chrono::time_point<std::chrono::system_clock>, std::string_view>

The compiler treats them as equivalent.

That is useful for readability, but it does not create stronger type safety. If you need a truly distinct type, use a struct or class.

Another small example

Alias templates are common with standard containers.

For example, if a project repeatedly uses a vector with a custom allocator, the full type can be hidden behind an alias template:

template <typename T>
using Buffer = std::vector<T, CustomAllocator<T>>;

Now the code can use:

Buffer<int>
Buffer<double>
Buffer<std::string>

instead of repeating the allocator every time.

The flexible part is still T. The repetitive part is fixed inside the alias.

In conclusion

An alias template gives a template-based name to another type. It is useful when part of a long template type should stay flexible. They improve readability without creating a new type.