Type Aliases (using vs typedef)

cardimg

In C, we use typedef to create a new name for an existing type (e.g., typedef unsigned long uint32_t;). 

In C++, while typedef still works, the using keyword is the modern, preferred way to define type aliases.

It offers clearer syntax, especially for function pointers and templates, and follows standard "assignment-style" logic (Name = Type).

Syntax & Usage

1. Basic Type Aliasing 

Replacing primitive types with semantic names.

// C-Style (Old)
typedef unsigned long TickCount;

// C++ Style (Modern)
using TickCount = unsigned long;

TickCount start_time = 0; // Usage is identical

2. Function Pointers (The Real Benefit) 

Defining function pointers with typedef is notoriously hard to read. using makes it look like a variable assignment.

// C-Style: Hidden logic inside the declaration
typedef void (*Callback)(int, int);

// C++ Style: Clear name = type logic
using Callback = void (*)(int, int);

void register_handler(Callback cb);

3. Template Aliases (C++ Exclusive) 

You cannot use typedef to create a partial alias for a template. You must use using.

template <typename T>
struct RingBuffer { /* ... */ };

// Define a standard buffer for floats
using AudioBuffer = RingBuffer<float>; 

Visualization

Think of using as creating a "Label" for a complex type.

Featuretypedefusing
Syntaxtypedef OldName NewName; (Backward)using NewName = OldName; (Forward)
ReadabilityPoor for function pointers.Excellent.
TemplatesNot supported.Fully supported.
CompatibilityC and C++.C++ only (C++11 and later).

Relevance in Embedded/Firmware

1. Hardware Abstraction 

You can define hardware-specific types in a header and swap them out easily without changing the driver code.

#ifdef STM32
    using RegType = uint32_t;
#else
    using RegType = uint16_t;
#endif

void write_register(RegType value);

2. Simplifying Complex Types 

Modern C++ types (like iterators or smart pointers) can get very long. Aliases keep code readable.

// Without alias
std::vector<SensorData>::iterator it = buffer.begin();

// With alias
using Iter = std::vector<SensorData>::iterator;
Iter it = buffer.begin();

3. Callback Signatures 

Firmware relies heavily on callbacks (ISRs, events). using makes API definitions self-documenting.

using ErrorHandler = void (*)(int error_code);
using DataHandler  = void (*)(const uint8_t* data, size_t len);

void setup_drivers(ErrorHandler err, DataHandler data);

Common Pitfalls (Practical Tips)

PitfallDetails
❌ Not a New Type

using does not create a new, distinct type. It is just a synonym.

using Voltage = int; and using Current = int; are effectively the same. The compiler will not stop you from passing Voltage to a function expecting Current.

❌ Obscurity

Overusing aliases can hide important details.

using Handle = void*; hides the fact that Handle is an unsafe void pointer. Sometimes seeing the raw type is safer.

✅ Global DefinitionsPut common aliases (like using byte = uint8_t;) in a central types.h header so the whole firmware uses consistent terminology.

 

 

 

 

Concept understood? Let's apply and learn for real

Practice now