53. ISR Callback Alias

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:

  1. Create a function pointer type alias named ISRCallback using the using keyword.
  2. The alias must represent a pointer to a function with the following signature:
    1. void callback(int32_t code);
      
  3. Two callback functions are already provided:
    • onError(int32_t code) — prints ERROR <code>
    • onComplete(int32_t code) — prints COMPLETE <code>
  4. Based on the input mode, register the appropriate callback.
  5. Invoke the callback indirectly through the function pointer alias.

 

Program Input:

Read two space-separated integers from standard input:

mode eventCode
  • mode determines which callback to register
  • eventCode is passed to the callback

 

Selection Logic:

  • If mode == 1 → register onError
  • If mode == 2 → register onComplete

After registration, invoke the callback using eventCode as the argument.

 

Example 1

Input:

1 42

Output:

ERROR 42 

 

Example 2

Input:

2 -5

Output:

COMPLETE -5 

 

Constraints:

  • mode will always be exactly 1 or 2
  • eventCode is a 32-bit signed integer
    Range: -2,147,483,648 to 2,147,483,647
  • You must use the using keyword to define the alias
  • You must call the function through the function pointer variable (not directly)

 

 

 

 

Need Help? Refer to the Quick Guide below

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.