Skip to content

Instantly share code, notes, and snippets.

@lsem
Last active July 30, 2022 11:26
Show Gist options
  • Save lsem/3ff245c24ac60cc8611945f77d92131f to your computer and use it in GitHub Desktop.
Save lsem/3ff245c24ac60cc8611945f77d92131f to your computer and use it in GitHub Desktop.
std_func_to_ptr.cpp
#include <any>
#include <cassert>
#include <functional>
#include <iostream>
#include <vector>
using namespace std;
template <class...> struct spell_type;
template <class...> struct type_list {};
// Helper meta-function needed to derive correct signature of our C-style
// function.
template <class F>
struct function_traits : public function_traits<decltype(&F::operator())> {};
template <class ClassType, class R, class... Args>
struct function_traits<R (ClassType::*)(Args...) const> {
using type = R (*)(Args...);
using return_type = R;
using args_type = type_list<Args...>;
};
// That is actually the thing: you give it std::function, it moves it into
// its internal storage (into slot Index of static array) and returns
// C-style function of the same signature which when called, executed your
// std function. See demo and tests.
template <size_t MaxSlot> class func_to_ptr_impl {
static_assert(MaxSlot > 0, "Need at least one slot");
public:
template <size_t Index, class F> static auto get(F f) {
return get_impl<Index>(typename function_traits<F>::args_type{}, std::move(f));
}
private:
template <size_t Index, class F, class... Args>
static auto get_impl(type_list<Args...>, F f) {
static std::any std_funcs[MaxSlot];
std_funcs[Index] = std::move(f);
return +[](Args... args) -> typename function_traits<F>::return_type {
auto &std_func = std::any_cast<F &>(std_funcs[Index]);
return std_func(args...);
};
}
};
// Front end function to use.
// You give it std function and 'converts' it to corresponding C-style pointer
// to function. Index must be unique per wrapped function.
template <size_t Index, size_t MaxSlot, class F> auto func_to_ptr(F f) {
return func_to_ptr_impl<MaxSlot>::template get<Index, F>(std::move(f));
}
//
// Compile tests that signature is coorect (see also runtime_tests())
//
static_assert(
std::is_same_v<decltype(func_to_ptr<0, 1>(std::function<void()>{})), void (*)()>);
static_assert(
std::is_same_v<decltype(func_to_ptr<0, 1>(std::function<int()>{})), int (*)()>);
static_assert(std::is_same_v<decltype(func_to_ptr<0, 1>(std::function<void(float &)>{})),
void (*)(float &)>);
static_assert(std::is_same_v<
decltype(func_to_ptr<0, 1>(std::function<void(float *, int &, char)>{})),
void (*)(float *, int &, char)>);
//
// Runtime tests that parameters are passed correctly (see also signature
// tests).
//
void tests() {
#undef NDEBUG
// we are going to (re)use one slot 0.
// no args
{
bool called = false;
func_to_ptr<0, 1>([&]() { called = true; })();
assert(called);
}
// r-value args
{
bool called = false;
func_to_ptr<0, 1>([&](int x, std::string y) {
called = true;
assert(x == 10);
assert(y == "test");
})(10, "test");
assert(called);
}
// l-value args
{
bool called = false;
int x = 20;
func_to_ptr<0, 1>([&](int &x, std::string y) {
called = true;
assert(x == 20);
assert(y == "test");
x = 100;
})(x, "test");
assert(called);
assert(x == 100);
}
// l-value const args
{
bool called = false;
func_to_ptr<0, 1>([&](const int &x, std::string y) {
called = true;
assert(x == 20);
assert(y == "test");
})(20, "test");
assert(called);
}
// C-style out param
{
bool result = false;
func_to_ptr<0, 1>([&](bool *result) { *result = true; })(&result);
assert(result);
}
std::cout << "all passed\n";
}
namespace demo {
// We are going to use enum for organizing auto-incrementing of slots.
namespace slots {
enum { notify_call, read_rady, __count };
}
// And use local shortcut called 'to_f' to get rid of specifying slots::__count
// for all invocations.
template <size_t Slot, class F> auto to_f(F f) {
return func_to_ptr<Slot, slots::__count>(std::move(f));
}
// This is a model of hypothetical subsystem that works on user provided C-style
// callbacks but does not accept void* as param (where we can pass e.g. this),
// making it inconvenient to use in classes.
struct bt_stack {
void on_notify(void (*cb)()) { m_on_notify_cb = cb; }
void on_read_ready(char (*cb)(int, float)) { m_on_read_ready_cb = cb; }
void kick_bt() {
static int d = 0;
if (m_on_notify_cb) {
m_on_notify_cb();
}
if (m_on_read_ready_cb) {
m_on_read_ready_cb(10, 20.0);
}
}
void (*m_on_notify_cb)() = nullptr;
char (*m_on_read_ready_cb)(int, float) = nullptr;
};
// And this is c++ class that is client of bt_stack and needs to receive
// callbacks.
struct client {
client() {
// turn our std functions which capture `this` into C-style func pointers.
// they will be valid as long as corresponding slot is not overwritten.
m_btstack.on_notify(to_f<slots::notify_call>(
[this]() { std::cout << m_counter++ << ": client::on_notify()\n"; }));
m_btstack.on_read_ready(to_f<slots::read_rady>([this](int i, float f) -> char {
std::cout << m_counter++ << ": client::on_read_ready(" << i << ", " << f
<< ")\n";
return '1';
}));
}
// make bt_stack call functions to see if our std functions get invoked.
void kick_bt() { m_btstack.kick_bt(); }
bt_stack m_btstack;
int m_counter = 0;
};
} // namespace demo
int main() {
std::cout << "- tests -\n";
tests();
std::cout << "\n- demo -\n";
demo::client c;
c.kick_bt();
c.kick_bt();
c.kick_bt();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment