HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

C-ifying a capturing lambda

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
capturingifyinglambda

Problem

What do you think of this code?

#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:

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.