Skip to content

Instantly share code, notes, and snippets.

@jancumps
Last active July 22, 2024 14:30
Show Gist options
  • Save jancumps/6dbc257c80911270775ee9d44a5131ee to your computer and use it in GitHub Desktop.
Save jancumps/6dbc257c80911270775ee9d44a5131ee to your computer and use it in GitHub Desktop.
C++ callback manager class with templated handler parameters
C++ callback manager class with templated handler parameters
check readme.MD

This callback manager can execute a method of a particular object as a callback. It can be used in typical callback situations, e.g.: in a UART driver. But also in interrupt handlers.

  • lightweight: small runtime cost, low firmware hit. Can be used for tiny embedded systems.
  • reusable: don't assume parameters and types for the handler, in this little framework -> C++ templates
  • object oriented, modern C++ constructs -> std::function, lambda, again C++ templates
  • type-safe -> again C++ templates
  • allow a classic C function, a lambda, a static class method or an object method as handler.
  • pass handler arguments by value, by reference, as const

Raspberry Pico

For Raspberry Pico C/C++ SDK users, I added a CMake file. You can put that, and the header file, in a subdir of your SDK project. Then register it as a library in your project CMake file:

add_subdirectory(callbackmanager)
# ...
target_link_libraries(${CMAKE_PROJECT_NAME} pico_stdlib callbackmanager)

Arduino

For Arduino, you can download callbackmanager.h to your computer, then use Sketch -> Add File ... to get the callbackmanager included to your project. Add this line to your sketch:

#include "callbackmanager.h"

Example sketch that tests the callback manager: test_arduino.ino
Arduinos that use the gcc-avg toolchain are not supported (UNO, Mega, the original basic Nano)

Blog:

see article on element14

and a follow-up that shows different callback options (object method, static class method, C function, lambda)

/*
* callbackmanager.h
*
* Created on: 16 jun. 2024
* Author: jancu
*/
#ifndef CALLBACKMANAGER_H_
#define CALLBACKMANAGER_H_
#include <functional>
template <typename R, typename... Args>
// restrict to arithmetic data types for return value, or void
#ifdef __GNUC__ // this requires a recent version of GCC.
#if __GNUC_PREREQ(10,0)
requires std::is_void<R>::value || std::is_arithmetic_v<R>
#endif
#endif
class Callback {
public:
Callback() : _callback(nullptr){}
inline void set(std::function<R(Args... args)> callback) {
_callback = & callback;
}
inline void unset() {
_callback = nullptr;
}
/*
* R can either be an arithmetic type, or void
*/
inline R call(Args... args) {
if constexpr (std::is_void<R>::value) {
if (_callback == nullptr) {
return;
}
(*_callback)(args...);
}
if constexpr (! std::is_void<R>::value) {
if (_callback == nullptr) {
return 0; // R can only be a arithmetic type. 0 should work as default.
}
return (*_callback)(args...);
}
}
inline bool armed() {
return (_callback != nullptr);
}
private:
std::function<R(Args... args)> *_callback;
};
#endif /* CALLBACKMANAGER_H_ */
// https://community.element14.com/products/devtools/software/f/forum/54719/c-callbacks-and-templates
#include <cstdio>
#include "callbackmanager.h"
class MyClass {
public:
inline int handler(const int& num1, const int& num2) const {
return num1 + num2;
}
static inline int staticHandler(const int& num1, const int& num2) {
return num1 + num2;
}
};
int functionHandler(const int& num1, const int& num2) {
return num1 + num2;
}
int main() {
int a = 4;
int b = 5;
{ // scenario: call object method
MyClass myClass;
Callback<int, const int&, const int&> cb;
// Use a lambda to capture myClass and call the object method
cb.set([&myClass](const int& num1, const int& num2) -> int {
return myClass.handler(num1, num2);
});
int o = cb.call(a, b);
printf("Value: %i\n", o); // We might be on an embedded system, use printf() and not std::cout
fflush(stdout);
}
{ // scenario: call static method
Callback<int, const int&, const int&> cb;
// Use a lambda to call the static method
cb.set([](const int& num1, const int& num2) -> int {
return MyClass::staticHandler(num1, num2);
});
int o = cb.call(a, b);
printf("Value: %i\n", o); // We might be on an embedded system, use printf() and not std::cout
fflush(stdout);
}
{ // scenario: call C function
Callback<int, const int&, const int&> cb;
// Use a lambda to call the classic C function
cb.set([](const int& num1, const int& num2) -> int {
return functionHandler(num1, num2);
});
int o = cb.call(a, b);
printf("Value: %i\n", o); // We might be on an embedded system, use printf() and not std::cout
fflush(stdout);
}
{ // scenario: call pure lambda
Callback<int, const int&, const int&> cb;
// Use a lambda to execute anonymous C code
cb.set([](const int& num1, const int& num2) -> int {
return num1 + num2;
});
int o = cb.call(a, b);
printf("Value: %i\n", o); // We might be on an embedded system, use printf() and not std::cout
fflush(stdout);
}
{ // scenario: use void
Callback<void, const int&, const int&> cb;
// Use a lambda to execute anonymous C code
cb.set([](const int& num1, const int& num2) {
return;
});
cb.call(a, b);
}
}
#include <cstdio>
#include <string>
#include "callbackmanager.h"
class MyClass {
public:
int handler(const int& num1, std::string& s) {
printf("Message: %s\n", s.c_str()); // We might be on an embedded system, use printf() and not std::cout
fflush(stdout);
return num1;
}
};
int main() {
MyClass myClass;
Callback<int, const int&, std::string&> cb;
// Use a lambda to capture myClass and call the member method
cb.set([&myClass](const int& num1, std::string& s) -> int {
return myClass.handler(num1, s);
});
int a = 4;
std::string s = "hey";
int o = cb.call(a, s);
printf("Value: %i\n", o); // We might be on an embedded system, use printf() and not std::cout
fflush(stdout);
}
# for Raspberry Pico / RP2040
add_library( callbackmanager
callbackmanager.h
)
# solve CMake can not determine linker language for target
# because the module has no source file
set_target_properties(callbackmanager PROPERTIES LINKER_LANGUAGE CXX)
target_link_libraries(callbackmanager
)
target_include_directories(callbackmanager PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
#include "callbackmanager.h"
// https://community.element14.com/products/devtools/software/f/forum/54719/c-callbacks-and-templates
#include <cstdio>
#include "callbackmanager.h"
#include <string>
class MyClass {
public:
inline int handler(const int& num1, const int& num2) const {
Serial.println("class member function callback ");
return num1 + num2;
}
static inline int staticHandler(const int& num1, const int& num2) {
Serial.println("static class member function callback ");
return num1 + num2;
}
};
int functionHandler(const int& num1, const int& num2) {
Serial.println("classic C function callback ");
return num1 + num2;
}
void setup() {
char msg[50];
Serial.begin(9600);
delay(4000); // enough time to open the Serial Monitor :)
Serial.println("starting up");
int a = 4;
int b = 5;
{ // scenario: call object method
MyClass myClass;
Callback<int, const int&, const int&> cb;
// Use a lambda to capture myClass and call the object method
cb.set([&myClass](const int& num1, const int& num2) -> int {
return myClass.handler(num1, num2);
});
int o = cb.call(a, b);
sprintf(msg, "Value: %i\n", o);
Serial.print(msg);
}
{ // scenario: call static method
Callback<int, const int&, const int&> cb;
// Use a lambda to call the static method
cb.set([](const int& num1, const int& num2) -> int {
return MyClass::staticHandler(num1, num2);
});
int o = cb.call(a, b);
sprintf(msg, "Value: %i\n", o);
Serial.print(msg);
}
{ // scenario: call C function
Callback<int, const int&, const int&> cb;
// Use a lambda to call the classic C function
cb.set([](const int& num1, const int& num2) -> int {
return functionHandler(num1, num2);
});
int o = cb.call(a, b);
sprintf(msg, "Value: %i\n", o);
Serial.print(msg);
}
{ // scenario: call pure lambda
Callback<int, const int&, const int&> cb;
// Use a lambda to execute anonymous C code
cb.set([](const int& num1, const int& num2) -> int {
Serial.println("lambda int function callback ");
return num1 + num2;
});
int o = cb.call(a, b);
sprintf(msg, "Value: %i\n", o);
Serial.print(msg);
}
{ // scenario: return a bool
Callback<bool, const int&, const int&> cb;
// Use a lambda to execute anonymous C code
cb.set([&msg](const int& num1, const int& num2) -> bool {
Serial.println("lambda bool function callback ");
return num1 == num2;
});
sprintf(msg, "Value: %s\n", cb.call(a, b) ? "true" : "false");
Serial.print(msg);
}
{ // scenario: use void
Callback<void, const int&, const int&> cb;
// Use a lambda to execute anonymous C code
cb.set([&msg](const int& num1, const int& num2) {
Serial.println("lambda void function callback ");
sprintf(msg, "Value a: %i, value b: %i\n", num1, num2);
Serial.print(msg);
return;
});
cb.call(a, b);
}
{ // scenario: use void, and no attributes
Callback<void> cb;
// Use a lambda to execute anonymous C code
cb.set([]() {
Serial.println("lambda void function with no parameters callback ");
Serial.println("hello ");
return;
});
cb.call();
}
/*
{ // scenario: use an unsupported (non-fundamental) type for return value R
// this will generate a compile error
Callback<std::string, const int&, const int&> cb;
}
*/
}
#include <limits.h>
void loop() {
delay(ULONG_MAX);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment