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.
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.
#include <functional>
template <typename R, typename... Args>
// restrict to arithmetic data types for return value, or void
requires std::is_void<R>::value || std::is_arithmetic_v<R>
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...);
}
}
private:
std::function<R(Args... args)> *_callback;
};
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 "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;
}
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.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);
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.call(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.call(a, b);
}
/*
{ // 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;
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:
The test code is available as a Gist.