Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a stateless lambda without an instance (only type)

I'm trying to write a wrapper for a "register callback" type of interface from a C library. The issue is quite complicated by the fact that, the library lets you register "variadic" functions by accepting a list of parameter definitions. Then at callback time, the function is expected to extract its arguments from a type-erased list of arguments. Good old C...

The interface I'm trying to create is to accept any function, even a lambda, and automatically generate all the machinery to correctly register this function, along with registering the arguments and extracting them during callback time. So the user should only have to type:

register_callback("Callback Name", [](int i){return i+0.5;});

Now this is theoretically possible, but at one point, I need to be able to call the lambda without any instance, but with access to only its type. I don't understand why, but the default constructor of a stateless (non-capturing) lambda is deleted, so I can't simply default-construct from its type. What I'm planning to do instead is a very dirty-looking, but should probably be OK way to work around this:

template <class Func, class RET, class ...ARGS>
struct from_stateless {
    static RET to_function(ARGS...args) {
        RET (*f)(ARGS...) = *(Func *)(0);
        return f(args...);
    }
};

so that from_stateless<LAMBDA_TYPE, RET, ARGS...>::to_function is actually a function pointer that I can call without an argument and more importantly one that I can pass as a TEMPLATE ARGUMENT.

Under normal circumstances, the line RET (*f)(ARGS...) = *(Func *)(0); would be suicide, but it really should be safe with this use case, isn't it? After all, the function pointer obtained after conversion couldn't in any universe possibly depend on the lambda instance.

So, the question is, is it safe to do this as long as I make sure that the type is indeed a stateless lambda? Or am I missing something? Note that the RET (*f)(ARGS...) = *(Func *)(0); would trigger a compiler error if a closure accidentally slips through.

CLARIFICATION: I can't just decay the lambda to a function pointer and register it, since the signature of the lambda is not compatible with the "Register" method. The register method expects a function with signature: void (*)(int number_of_args, TypeErasedValue * arguments, TypeErasedValue * result) So you see, I need to (and already do), through template metaprograming, generate a free function templated on the type of the lambda to act as an adaptor between the expected and the actual signatures.

like image 630
enobayram Avatar asked Dec 20 '22 03:12

enobayram


2 Answers

It gets even easier than @Yakk's answer if you take advantage of the fact that a lambda can see the static locals of its enclosing scope without needing to capture them. Using this fact you can wrap the user-provided stateless lambda in your own stateless lambda with the required signature, and convert the latter into a function pointer.

using callback = void (*)(int, TypeErasedValue*, TypeErasedValue*);

template <typename F>
callback stateless_to_callback(F f) {
    static F static_f = f;
    return [](int argc, TypeErasedValue* argv, TypeErasedValue* result) {
        // extract arguments from argv
        auto r = static_f( /* arguments... */ );
        // store r in result
    };
}
like image 169
Oktalist Avatar answered Dec 21 '22 15:12

Oktalist


Want defined behaviour?

Add:

static Func state(Func* op=0){
  static Func f=*op;
  return f;
}

to your template class. First time you call it, pass a ptr to lambda. 2nd time, extract value for free.

The static Func f is constructed the first time the function is called: so long as the pointer is non-null that time, we are good. Every other time it is called, the pointer can be null and it does not use it, as static locals are only constructed once.

Call it to 'register' with pointer to lambda. Elsewhere (at callback), call it to get a copy. Lambda is stateless, so copy is free.

like image 40
Yakk - Adam Nevraumont Avatar answered Dec 21 '22 17:12

Yakk - Adam Nevraumont