Search code examples
c++c++17parameter-pack

Can I branch on argument type to parameter pack in C++17?


Current (old) function call signature:

        long vmcall(const std::string& function_name,
                    std::vector<address_t> args);

Now since I can only call into the guest using (address-sized) integers, this is quite a limited interface, and I want to expand it to at least also handle floating point values, and possibly others, like so:

        template <typename... Args>
        long vmcall(const std::string& function_name,
                    Args&&... args);

That will be the new function signature, replacing the old one.

To show how the args vector is currently used:

template <int W>
inline void Machine<W>::setup_call(
        address_t call_addr, address_t retn_addr,
        std::vector<address_t> args)
{
    assert(args.size() <= 8);
    cpu.reg(RISCV::REG_RA) = retn_addr;
    for (size_t arg = 0; arg < args.size(); arg++) {
        cpu.reg(RISCV::REG_ARG0 + arg) = args[arg];
    }
    cpu.jump(call_addr);
}

This function is called from vmcall with std::move(args), and all it does is copy the integer arguments in the args vector into the integral CPU registers, incrementally. This works fine when I want to call a function that only takes integral arguments. So, if I wanted to call a function that, say, takes one floating-point argument, then there is no way to do that. This is because FP registers are completely separate, and need to be handled differently.

Also, 32-bit and 64-bit floats are handled differently through NaN-boxing. So, it would be nice if it was possible to differentiate between float and double as well.

I'm not so strong on template magic to begin with. How do I branch on the type of each element in the parameter pack?


Solution

  • In C++17, the way to iterate over a parameter pack is to use a comma fold:

    (<expr>, ...); // expr contains Args and/or args
    

    If a single expression is insufficient, you can use an immediately-invoked lambda:

    ([&] {
        <statements> // statements contains Args and/or args
     }(), ...);
    

    Combine this with if constexpr on the types of your arguments:

    std::vector<address_t> addr_args;
    std::vector<fp_t> fp_args;
    ([&] {
        if constexpr (std::is_integral_v<Args>)
            addr_args.push_back(args);
        else
            fp_args.push_back(args);
     }(), ...);