Calling a stateless lambda without an instance (only type)

房东的猫 提交于 2021-02-07 08:01:49

问题


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.


回答1:


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
    };
}



回答2:


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.




回答3:


Assuming you have something to register normal function

template <typename Ret, typename... Args>
void register_callback(const std::string& s, Ret(*)(Args...))
{
    std::cout << "register function " << s << std::endl;
    // Implementation
}

You may simply explicitly convert your non-capturing lambda in pointer on function:

template <typename Lambda>
void register_callback(const std::string& s, const Lambda&f)
{
    // convert non capturing lambda into function pointer
    Register(s, +f);
}

Live demo

or change your call site to something like:

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

Note: You may use * instead of + for the decay.



来源:https://stackoverflow.com/questions/30681650/calling-a-stateless-lambda-without-an-instance-only-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!