patterncppMinor
C-ifying a capturing lambda
Viewed 0 times
capturingifyinglambda
Problem
What do you think of this code?
It can be used to supply a capturing lambda as a C callback:
If you need the same callback across multiple threads, or when thread_local is unimplemented in your compiler, you can remove the
To fix the problem in the answer, one can make use of the
#include
namespace
{
template
inline F cify(L&& l, R (*)(A...) noexcept(noexcept(
::std::declval()(::std::declval()...))))
{
static thread_local L l_(::std::forward(l));
static thread_local bool full;
if (full)
{
l_.~L();
new (static_cast(&l_)) L(::std::forward(l));
}
else
{
full = true;
}
struct S
{
static R f(A... args) noexcept(noexcept(
::std::declval()(::std::forward(args)...)))
{
return l_(::std::forward(args)...);
}
};
return &S::f;
}
}
template
F cify(L&& l)
{
return cify(::std::forward(l), F());
}It can be used to supply a capturing lambda as a C callback:
int main()
{
int a;
auto const f(cify([a]{::std::cout << "a: " << a << ::std::endl;}));
f();
return 0;
}If you need the same callback across multiple threads, or when thread_local is unimplemented in your compiler, you can remove the
thread_local keyword.To fix the problem in the answer, one can make use of the
__COUNTER__ macro. It is only one of numerous possible solutions.std::vector callbacks;
template
void add_callback (int x)
{
callbacks.emplace_back(cify([x] () { std::cout (1);
add_callback(2);
for (auto& callback : callbacks) {
callback();
}
return 0;
}Solution
There's an existing technique which is not dissimilar referred to as "exception vomiting". Observe:
This abuses the fact that the compiler must provide a thread-local stack for complex objects for use as exception storage, regardless of their support for other thread local features, and therefore enjoys much broad compiler support. The most obvious drawbacks are a) it's horrible, and b) it's limited to stack semantics.
You could consider a less terrible version as follows assuming the relevant compiler support:
Your cify function is nothing more than
The general case cannot be solved without the use of a JIT to create new functions at runtime.
void f(void(*p)()) {
p();
}
template void real_f(F func) {
try {
throw func;
} catch(...) {
f([] {
try {
throw;
} catch(F func) {
func();
}
});
}
}This abuses the fact that the compiler must provide a thread-local stack for complex objects for use as exception storage, regardless of their support for other thread local features, and therefore enjoys much broad compiler support. The most obvious drawbacks are a) it's horrible, and b) it's limited to stack semantics.
You could consider a less terrible version as follows assuming the relevant compiler support:
template void real_f(F func) {
thread_local std::vector> funcs;
funcs.push_back(func);
f([] { funcs.back()(); });
funcs.pop_back();
}Your cify function is nothing more than
real_f as posted here, but a bit more generic and a lot less reliable as you can't handle multiple instances properly even with the use of __COUNTER__.The general case cannot be solved without the use of a JIT to create new functions at runtime.
Code Snippets
void f(void(*p)()) {
p();
}
template<typename F> void real_f(F func) {
try {
throw func;
} catch(...) {
f([] {
try {
throw;
} catch(F func) {
func();
}
});
}
}template<typename F> void real_f(F func) {
thread_local std::vector<std::function<void()>> funcs;
funcs.push_back(func);
f([] { funcs.back()(); });
funcs.pop_back();
}Context
StackExchange Code Review Q#79612, answer score: 8
Revisions (0)
No revisions yet.