6

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 )

Community
  • 1
  • 1
  • As `EXTENSIONFUNCTION` doesn't seems to take a `void* userData` to be able to call your class `Extension`, I don't see how to solve your problem. – Jarod42 Dec 17 '14 at 08:22
  • What is `NATIVEVALUE`? Maybe you can pass a pointer to `Extension` in the first element of the array. – Anton Savin Dec 17 '14 at 08:22
  • I had a somewhat difficult time reading what you put in here, but I *think* maybe its [something like this](http://ideone.com/fJlkK6). If not, I hope it at least gives you some ideas. Best of luck. – WhozCraig Dec 17 '14 at 11:41
  • @WhozCraig Thanks...sorry if the question was unclear, I elaborated. But it's actually the reverse problem. My goal is not to call an EXTENSIONFUNCTION in a pretty way--it's rather to be able to take a routine expressed as a std::function and allow the C project to call it as if it had an EXTENSIONFUNCTION signature. So I need to fabricate a "shim" function pointer that takes this NATIVEVALUE array, that invokes the std::function with Value class instances. Does that make sense? – HostileFork says dont trust SE Dec 17 '14 at 15:55
  • 1
    Yes, it makes more sense now. I originally thought you wanted to invoke a variadic function as a C-style callback, passing the variadic parameters down to the callback packed as `NATIVEVALUE`, which is obviously doable. The reverse, *unpacking* a `NATIVEVALUE[]` will be considerably harder. I'd have to mull that over, preferably with a good glass of single-malt =P. – WhozCraig Dec 17 '14 at 19:31

1 Answers1

2

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*.

Morwenn
  • 21,684
  • 12
  • 93
  • 152