Last active
July 30, 2022 11:26
-
-
Save lsem/3ff245c24ac60cc8611945f77d92131f to your computer and use it in GitHub Desktop.
std_func_to_ptr.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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