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 )
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*
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With