In embedded systems, Interrupt Service Routines (ISRs) often notify the main firmware logic by calling a registered callback function. This design decouples low-level hardware events from higher-level application behavior and is commonly used in drivers and hardware abstraction layers (HALs).
Your task is to define a function pointer type alias using modern C++ syntax, select the correct callback based on a hardware mode input, and invoke the callback safely through the alias.
What You Must Do:
using keyword.void callback(int32_t code);
onError(int32_t code) — prints ERROR <code>onComplete(int32_t code) — prints COMPLETE <code>
Program Input:
Read two space-separated integers from standard input:
mode eventCodemode determines which callback to registereventCode is passed to the callback
Selection Logic:
mode == 1 → register onErrormode == 2 → register onCompleteAfter registration, invoke the callback using eventCode as the argument.
Example 1
Input:
1 42Output:
ERROR 42
Example 2
Input:
2 -5
Output:
COMPLETE -5
Constraints:
mode will always be exactly 1 or 2eventCode is a 32-bit signed integer-2,147,483,648 to 2,147,483,647using keyword to define the alias
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).
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 identical2. 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>; Think of using as creating a "Label" for a complex type.
| Feature | typedef | using |
|---|---|---|
| Syntax | typedef OldName NewName; (Backward) | using NewName = OldName; (Forward) |
| Readability | Poor for function pointers. | Excellent. |
| Templates | Not supported. | Fully supported. |
| Compatibility | C and C++. | C++ only (C++11 and later). |
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);| Pitfall | Details |
|---|---|
| ❌ Not a New Type |
|
| ❌ Obscurity | Overusing aliases can hide important details.
|
| ✅ Global Definitions | Put common aliases (like using byte = uint8_t;) in a central types.h header so the whole firmware uses consistent terminology. |