It's possible to fill the appropriate registers of a virtual machine based on the argument list and some logic using a C++17 fold, like so: https://github.com/fwsGonzo/libriscv/blob/master/lib/libriscv/machine_vmcall.hpp#L35
https://github.com/fwsGonzo/libriscv/blob/master/lib/libriscv/machine_vmcall.hpp#L18
Structs will be pushed onto the stack and the address will take up an integer register slot. So, I can turn a regular function call into a call into my virtual machine. I don't think any other programming language can do that.
Now, for the other way around there are system call handlers. They look like this: https://github.com/fwsGonzo/libriscv/blob/master/emulator/src/syscalls.cpp#L20
In an effort to simplify system call handling I want to be able to take a list of argument types, perform some logic on each of them (extract the values based on type), and then optionally call a lambda with the arguments I built up. For example like this:
template <int, int, int>
void simplified_systemcall_handler(machine& machine, std::function<int, int int> callback)
{
parameter_pack p;
int i = 0;
foreach (Arg : <int, int, int>) {
if constexpr (std::is_integral_v<Arg>) {
p.append(machine.get_syscall_arg<int> (i++));
}
}
callback(p);
}
This, of course, won't work as you can't build parameter packs to my knowledge, nor iterate template parameters like that. However, the pseudo-code tells you what I want to do.
The goal is to make system call handling simpler and simplify the API greatly by not having to extract the system call arguments one line at a time. So, what is the second best thing?
What about being able to extract several parameters in one go, but how can you temporarily store that and iterate it in C++? Something like this:
auto list = machine.get_syscall_arguments<int, long, float> ();
// process list assuming list[0] is int, list[1] is long
The number and types of arguments is known beforehand. The values are not known until they are extracted from the machine registers.
Hmm, is this something you're after?
#include <iostream>
#include <type_traits>
template<class... Args> void omg(Args &&...args)
{
((std::is_integral_v<Args> && std::cout << args << " is integral\n"), ...);
}
int main() {
omg(1, 42, 3.14, "Hi!", 0<<0);
}
1 is integral
42 is integral
0 is integral
operator,
could be the Swiss army knife of unary foreach fold-expressions.
You don't even need actual values for that:
template<class... Args> void omg()
{
std::size_t i{};
((++i,
std::is_integral_v<Args> && std::cout <<
"Arg #" << i << " is integral\n"
|| std::is_scalar_v<Args> && std::cout <<
"Arg #" << i << " is not integral, yet is scalar\n"
), ...);
}
int main() {
omg<int, int, double, char const *, std::size_t>();
}
If you don't have actual values at hand, but their types and index access, well, you can easily retrieve them through some very basic way:
template<class... Args, std::size_t... indices> void add_args(
std::index_sequence<indices...>)
{
(p.append(sys.get_arg<Args>(indices)), ...);
}
template<class... Args> void add_args() {
add_args<Args...>(std::index_sequence_for<Args...>{});
}
Even storing them in a tuple is a bit tricky and not quite straightforward:
std::tuple<std::decay_t<Args>...> retval;
((std::get<indices>(retval) = sys.get_arg<Args>(indices)), ...);
return retval;