c++c++17template-meta-programmingparameter-pack

C++ iterate template parameters


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.


Solution

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