Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proxying a std::function to a C function that wants an array of arguments

I'm dealing with a C system that offers a hook of this form:

int (*EXTENSIONFUNCTION)(NATIVEVALUE args[]);

It's possible to register an EXTENSIONFUNCTION and the number of arguments it takes.

My idea was that I'd make a class Extension to wrap up an extension. It would be able to be constructed from a std::function (or any Callable, ideally, but let's just say it contains a std::function for now). And the extension takes Value parameters, which wrap up NATIVEVALUE (but are larger). I'd automatically take care of the parameter count with sizeof...(Ts), for instance. It might look like this:

Extension<lib::Integer, lib::String> foo =
    [](lib::Integer i, lib::String s) -> int {
        std::cout << i;
        std::cout << s;
        return 0;
    }

Problem is that in order for the C library to register and call it, it wants that array-based interface. :-/

I set out to try and get the compiler to write a little shim, but I don't see a way to do it. I can have a variadic operator() on Extension, and do a runtime loop over the NATIVEVALUE to get an array of Value[]. But what do I do with that? I can't call the std::function with it.

So it seems I need to make an EXTENSIONFUNCTION instance which calls my std::function, as a member of each Extension instance.

But basically I find myself up against a wall where I have a variadic templated class for the extension... and then a sort of "can't get there from here" in terms of taking this NATIVEVALUE args[] and being able to call the std::function with them. If std::function would be willing to be invoked with a std::array of arguments, that would solve it, but of course that isn't how it works.

Is it possible to build a shim of this type? The "ugly" thing I can do is just proxy to another array, like:

Extension<2> foo =
    [](lib::Value args[]) -> int {
        lib::Integer i (args[0]);
        lib::String s (args[1]);
        std::cout << i;
        std::cout << s;
        return 0;
    }

But that's not as ergonomic. It seems impossible, without knowing the calling convention and doing some kind of inline assembly stuff to process the parameters and CALL the function (and even that would work for functions only, not Callables in general). But people here have proven the impossible possible before, usually by way of "that's not what you want, what you actually want is..."


UPDATE: I just found this, which seems promising...I'm still trying to digest its relevance:

"unpacking" a tuple to call a matching function pointer

( Note: There are a few cross-cutting issues in what I aim to do. Another point is type inference from lambdas. Answer here seems to be the best bet on that... it appears to work, but I don't know if it's "kosher": Initialize class containing a std::function with a lambda )

like image 264
HostileFork says dont trust SE Avatar asked Dec 17 '14 07:12

HostileFork says dont trust SE


1 Answers

If I managed to reduce the problem to its simplest form, you need a way to call an std::function taking its argument from a fixed-sized C-style array without having to create a run-time loop. Then, these functions may solve your problem:

template<std::size_t N, typename T, typename F, std::size_t... Indices>
auto apply_from_array_impl(F&& func, T (&arr)[N], std::index_sequence<Indices...>)
    -> decltype(std::forward<F>(func)(arr[Indices]...))
{
    return std::forward<F>(func)(arr[Indices]...);
}

template<std::size_t N, typename T, typename F,
         typename Indices = std::make_index_sequence<N>>
auto apply_from_array(F&& func, T (&arr)[N])
    -> decltype(apply_from_array_impl(std::forward<F>(func), arr, Indices()))
{
    return apply_from_array_impl(std::forward<F>(func), arr, Indices());
}

Here is an example demonstrating how it can be used:

auto foo = [](int a, int b, int c)
    -> int
{
    return a + b + c;
};

int main()
{
    Value arr[] = { 1, 2, 3 };

    std::cout << apply_from_array(foo, arr); // prints 6
}

Of course, with the signature int (*)(T args[]), args is just a T* and you don't know its size at compile time. However, if you know the compile time size from somewhere else (from the std::function for example), you can still tweak apply_from_array to manually give the compile-time size information:

template<std::size_t N, typename T, typename F, std::size_t... Indices>
auto apply_from_array_impl(F&& func, T* arr, std::index_sequence<Indices...>)
    -> decltype(std::forward<F>(func)(arr[Indices]...))
{
    return std::forward<F>(func)(arr[Indices]...);
}

template<std::size_t N, typename T, typename F,
         typename Indices = std::make_index_sequence<N>>
auto apply_from_array(F&& func, T* arr)
    -> decltype(apply_from_array_impl<N>(std::forward<F>(func), arr, Indices()))
{
    return apply_from_array_impl<N>(std::forward<F>(func), arr, Indices());
}

And then use the function like this:

int c_function(NATIVEVALUE args[])
{
    return apply_from_array<arity>(f, args);
}

In the example above, consider that f is an std::function and that arity is the arity of f that you managed to get, one way or another, at compile time.

NOTE: I used the C++14 std::index_sequence and std::make_index_sequence but if you need your code to work with C++11, you can still use handcrafted equivalents, like indices and make_indices in the old question of mine that you linked.


Aftermath: the question being about real code, it was of course a little bit more complicated than above. The extension mechanism is designed so that everytime an extension function is called, C++ proxys above the C API (lib::Integer, lib::String, etc...) are created on the fly then passed to the user-defined function. This required a new method, applyFunc in Extension:

template<typename Func, std::size_t... Indices>
static auto applyFuncImpl(Func && func,
                          Engine & engine,
                          REBVAL * ds,
                          utility::indices<Indices...>)
    -> decltype(auto)
{
    return std::forward<Func>(func)(
        std::decay_t<typename utility::type_at<Indices, Ts...>::type>{
            engine,
            *D_ARG(Indices + 1)
        }...
    );
}

template <
    typename Func,
    typename Indices = utility::make_indices<sizeof...(Ts)>
>
static auto applyFunc(Func && func, Engine & engine, REBVAL * ds)
    -> decltype(auto)
{
    return applyFuncImpl(
        std::forward<Func>(func),
        engine,
        ds,
        Indices {}
    );
}

applyFunc takes the function to call an calls it with instances of the appropriate types (Integer, String, etc...) on the fly from the underlying C API created on the fly with an Engine& and a REBVAL*.

like image 157
Morwenn Avatar answered Nov 15 '22 00:11

Morwenn