Skip to content
C++
Language
since C++98
Basic

Type Aliases

"C++ type aliases: using declarations, typedef, alias templates, dependent type extraction, and pitfalls for working engineers."

Type Aliassince C++98 / C++11

A type alias introduces an alternative name for an existing type without creating a distinct type; typedef (C++98) and using (C++11) are semantically equivalent for simple aliases, but only using can define alias templates.

Overview

Both typedef and using produce transparent synonyms: the alias and its target are the same type to the compiler. Overload resolution, template specialization, and std::is_same all treat them as identical.

cpp
typedef unsigned long long u64;
using u64 = unsigned long long;  // C++11 β€” identical effect

static_assert(std::is_same_v<u64, unsigned long long>);  // C++17 std::is_same_v

The using form should be the default in C++11 and later. It reads left-to-right, handles function pointer types without inside-out parsing, and β€” critically β€” supports alias templates that typedef cannot express.

Syntax

cpp
// C++98: typedef
typedef int (*CompareFn)(const void*, const void*);    // function pointer β€” confusing
typedef std::map<std::string, std::vector<int>> Index; // verbose but legal

// C++11: using β€” same semantics, cleaner syntax
using CompareFn = int(*)(const void*, const void*);
using Index     = std::map<std::string, std::vector<int>>;

// C++11: alias template β€” typedef has no equivalent
template<typename T>
using Vec = std::vector<T>;

template<typename K, typename V>
using Dict = std::unordered_map<K, V>;

The typedef syntax places the new name somewhere in the middle of the declaration β€” particularly disorienting for function pointers and pointer-to-member types. The using syntax always puts the name on the left.

Alias Templates

Alias templates parameterize a type alias over one or more template arguments. The pre-C++11 workaround required a nested type member inside a helper struct; alias templates eliminate that boilerplate entirely.

cpp
// Pre-C++11: "template typedef" via nested struct
template<typename T>
struct VecHelper { typedef std::vector<T> type; };
typename VecHelper<int>::type v;  // verbose call site

// C++11: alias template β€” direct
template<typename T>
using Vec = std::vector<T>;
Vec<int> v;

// Partial binding: fix one argument, leave the other open
template<typename T>
using PoolVec = std::vector<T, PoolAllocator<T>>;

// PMR containers β€” C++17
template<typename T>
using PmrVec = std::pmr::vector<T>;  // C++17

Alias templates are transparent: they expand to the underlying type and do not produce a new independent template. This has important consequences described in the Pitfalls section.

Fixing Allocator Policies

Alias templates let you pin a shared configuration once and propagate it across a related family of types:

cpp
template<typename T>
using PoolString = std::basic_string<char, std::char_traits<char>, PoolAllocator<char>>;

// C++17: polymorphic memory resource variants
template<typename T>
using PmrString = std::basic_string<char, std::char_traits<char>,
                                    std::pmr::polymorphic_allocator<char>>;

Changing the allocator strategy requires editing one alias, not every declaration site.

Dependent Type Extraction

When a type name depends on a template parameter, typename is required. Alias templates can absorb that typename so call-site code stays clean:

cpp
// Without alias: typename at every use
template<typename C>
void process(C& c) {
    typename C::value_type  x  = c.front();
    typename C::iterator    it = c.begin();
}

// C++11: extract once via alias template
template<typename T> using ValueTypeOf = typename T::value_type;
template<typename T> using IteratorOf  = typename T::iterator;

template<typename C>
void process(C& c) {
    ValueTypeOf<C> x  = c.front();
    IteratorOf<C>  it = c.begin();
}

From C++20, concepts constrain template parameters structurally, which often eliminates explicit extraction aliases β€” constrained code can name associated types directly through concept requirements.

Class Member Aliases

STL-compatible containers expose a standardized set of nested aliases so generic algorithms can introspect them without knowing the concrete type:

cpp
class ByteBuffer {
public:
    using value_type      = std::byte;        // C++17
    using size_type       = std::size_t;
    using difference_type = std::ptrdiff_t;
    using pointer         = value_type*;
    using const_pointer   = const value_type*;
    using reference       = value_type&;
    using const_reference = const value_type&;
    using iterator        = pointer;
    using const_iterator  = const_pointer;

    iterator begin() noexcept { return data_; }
    iterator end()   noexcept { return data_ + size_; }

private:
    value_type* data_{};
    size_type   size_{};
};

// Generic algorithm reads value_type without knowing the container
template<typename Container>
auto first_element(const Container& c) -> typename Container::value_type {
    return *c.begin();
}

This pattern is how std::iterator_traits, range-based algorithms, and C++20 std::ranges concepts discover element types at compile time.

Best Practices

Prefer using over typedef unconditionally in C++11 and later. Keep typedef only when maintaining C++03 code or writing headers that must compile as C.

Name aliases with type conventions. Use PascalCase for public-facing aliases (EventHandlerPtr), and lowercase with underscores for STL-style nested aliases (value_type, size_type).

Prefer alias templates over macros for abbreviating long template instantiations. Aliases participate in the type system; macros don't.

cpp
// Avoid: macro loses type identity and doesn't compose with templates
#define STRMAP std::unordered_map<std::string, std::string>

// Prefer: alias participates in overload resolution and deduction
using StrMap = std::unordered_map<std::string, std::string>;

Use alias templates to centralize policy parameters (allocators, deleters, traits) rather than threading them through every declaration.

Common Pitfalls

Alias templates cannot be partially specialized

Alias templates expand transparently. The compiler does not accept partial specializations of them:

cpp
template<typename T>
using Ptr = std::unique_ptr<T>;

// Error: partial specialization of alias template
template<typename T>
using Ptr<T[]> = std::unique_ptr<T[]>;  // ill-formed

The workaround is a traits struct with a nested type member exposed via an alias:

cpp
template<typename T>        struct PtrTraits      { using type = std::unique_ptr<T>; };
template<typename T>        struct PtrTraits<T[]>  { using type = std::unique_ptr<T[]>; };
template<typename T> using  Ptr = typename PtrTraits<T>::type;

CTAD does not fire through alias templates before C++20

Class Template Argument Deduction (C++17) deduces template arguments from constructor calls but does not propagate through alias templates in C++17:

cpp
template<typename T>
using Pair = std::pair<T, int>;

auto p = Pair{3.14, 42};  // ill-formed in C++17 β€” CTAD doesn't apply to alias templates
                           // valid in C++20, which added alias template CTAD

C++20 allows deduction guides to fire through alias templates, making this pattern work.

Template template parameters and alias templates (pre-C++17 mismatch)

Prior to C++17, template template parameters required an exact match on the number of template parameters. Because std::vector<T> has two parameters (T and Allocator), passing an alias template that wraps it to a template template parameter expecting one parameter was ill-formed:

cpp
template<template<typename> class Container>
void fill(Container<int>& c, int val);

template<typename T>
using Vec = std::vector<T>;  // expands to vector<T, allocator<T>> β€” two params

fill<Vec>(v, 0);  // ill-formed in C++14; valid in C++17 (relaxed matching)

C++17 relaxed template template parameter matching to allow this, but if you target C++14 you need a dedicated single-parameter wrapper.

Aliases don't create new types β€” strong typedefs require more work

An alias and its underlying type are the same type in every overload resolution and specialization context:

cpp
using Meters  = double;
using Seconds = double;

void f(Meters m);
void f(Seconds s);  // Error: redefinition β€” both are double

For a genuinely distinct type, use a wrapper struct, enum class with a single enumerator, or a library like boost::units. A plain alias gives you readability, not type safety.

See Also

  • Templates β€” alias templates require understanding of template declaration syntax
  • CTAD β€” C++17 class template argument deduction and C++20 alias template CTAD
  • CRTP β€” alias templates reduce CRTP boilerplate in policy hierarchies
  • auto β€” auto and alias templates are complementary tools for reducing type verbosity