I developed a templated C++ callback manager: C++ callbacks and templates . I claimed that it can call back
- a classic C function,
- a lambda,
- a static class method or
- an object method,
and it supports these return types:
- void
- bool
- number
- objects (if the class has a default constructor and "is returnable".
But in that first post, I only showed an example where the method of an object was used as callback handler. In this post, I show all 4 scenarios. This time, I'm not showing the power of templates, but the power of lambda functions.
As a refresher, here is the Callback manager. Unchanged since previous post.
module; #include <concepts> #include <functional> export module callbackmanager; namespace callbackmanager { // concept guards what types of return values we can handle template<typename R> concept Callbackable = std::is_void<R>::value || std::is_arithmetic_v<R> || std::is_class_v<R>; export template <Callbackable R, typename... Args> class Callback { using callbackfunction_t = std::function<R(Args...)>; public: Callback() : callback_(nullptr), is_callback_set(false){} inline void set(callbackfunction_t callback) { callback_ = callback; is_callback_set = true; } inline void unset() { callback_ = nullptr; is_callback_set = false; } [[deprecated("Use operator () instead.")]] inline R call(Args... args) { return *this(args...); } inline bool is_set() { return is_callback_set; } /* * R can be an arithmetic type, an object, or void */ inline R operator()(Args... args) { if constexpr (std::is_void<R>::value) { // void if (!is_callback_set) { return; } else { (callback_)(args...); } } else if constexpr (std::is_class<R>::value) { // object if (!is_callback_set) { return R(); } else { return (callback_)(args...); } } else { // not void nor object if (!is_callback_set) { return 0; // R can only be a arithmetic type. 0 should work as default. } else { return (callback_)(args...); } } } private: callbackfunction_t callback_; bool is_callback_set; }; } // namespace callbackmanager
And here is the code that exercises the 4 scenarios. Each scenario is surrounded by {}. That means that they run in their own little isolated scope, And they don't spoil each other.
First, some code that will be used in the test. They are 3 different types of functions / methods:
- MyClass, now with a static and normal member function. So that we can test a scenario where we call an object member, and a scenario where we call a static class member.
- classic C function functionHandler().
All 3 do the same thing. They return the sum of two integers.
#include <cstdio> #include <string> #include <typeinfo> import callbackmanager; 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; }
Then the 4 tests:
int main() { int a = 4; int b = 5; 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(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(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(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(a, b); printf("Value: %i\n", o); fflush(stdout); } { // scenario: return a bool Callback<bool, const int&, const int&> cb; // Use a lambda to execute anonymous C code cb.set([](const int& num1, const int& num2) -> bool { return num1 == num2; }); printf("Value: %s\n", cb(a, b) ? "true" : "false"); 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(a, b); } { // scenario: return an object class return_class { public: return_class() : value_(0) {}; int value_; }; callbackmanager::Callback<return_class> cb; // Use a lambda to execute anonymous C code cb.set([]() -> return_class { return_class rc; rc.value_ = 1; return rc; }); return_class ret = cb(); printf("Value: %d\n", ret.value_ ); fflush(stdout); } /* { // 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; } }
edit: The template also allows void return values. Some more tests:
{ // 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) { printf("void gets num1: %i, num2: %i\n", num1, num2); fflush(stdout); return; }); cb.call(a, b); } { // scenario: use void, and no attributes Callback<void> cb; // Use a lambda to execute anonymous C code cb.set([]() { printf("void with no parameters\n"); fflush(stdout); return; }); cb.call(); }
Scenarios, in order of execution: (edit: this was written before I revised the code to support configurable return value. The test code above is adapted, but I didn't change the images below)
- call object method
In this one, I create object myClass of type MyClass. Then pass its handle() method as callback.
This was the scenario of the first post. - call static class method
In this test, I don't declare an object of type MyClass. I pass the static MyClass::staticHandle() - classic C function
Here, the callback gets a very common C function, named functionHandler(), as handler. - a pure lambda.
In this test, I pass an anonymous block of code as handler:
return num1 + num2; - return an object. This is added on 09-mar-2025.Works as long as the object supports a default constructor and does not forbid copy / assign.
In each test, the Callback object cb will call the code that's given to it in the set() call. All using the exact same little Callback template class. If you would set breakpoints, here is where the code would stop for each of the 4 cases:
edit: this was written before I replaced call() by operator ()..In your code, don't use cb.call(args), but cb(args). The test code in the blog is adapted, but I didn't change the images.
The test code is available as a Gist.