Search code examples
c++variadic-templates

How to iterate over a parameter pack and switch on parameter type?


I have a variadic function accepting any number of mixed parameters: the following code works as expected:

template <typename ... Args>
void call_snippet(lua_State *L, const std::string& name, Args... args) {
    lua_rawgeti(L, LUA_REGISTRYINDEX, snippets[name]);

    int nargs = 0;
    for (auto &&x : {args...}) {
        lua_pushinteger(L, x);
        nargs++;
    }
    lua_pcall(L, nargs, LUA_MULTRET, 0);
}

but falls short from needs because it assumes all parameters are int (or convertible to int). I would need something along the lines:

template <typename ... Args>
void call_snippet(lua_State *L, const std::string& name, Args... args) {
    lua_rawgeti(L, LUA_REGISTRYINDEX, snippets[name]);

    int nargs = 0;
    for (auto &&x : {args...}) {
        switch (typeof(x)) {
        case int:
            lua_pushinteger(L, x);
            nargs++;
            break;
        case float:
            lua_pushnumber(L, x);
            nargs++;
            break;
        case std:string:
            lua_pushcstring(L, x.c_str());
            nargs++;
            break;
        case char*:
            lua_pushcstring(L, x);
            nargs++;
            break;
        default:
            //raise error
            ;
    }
    lua_pcall(L, nargs, LUA_MULTRET, 0);
}

How should I actually implement the above pseudocode?


Solution

  • You should be able create function overloads for calling lua_push... and use a fold expression instead of the loop. The sizeof... operator can be used to determine the number of parameters:

    void Push(lua_State* l, std::nullptr_t) = delete;
    
    void Push(lua_State* l, std::string const& str)
    {
        lua_pushcstring(l, str.c_str());
    }
    
    void Push(lua_State* l, char const* str)
    {
        lua_pushcstring(l, str);
    }
    
    void Push(lua_State* l, int value)
    {
        lua_pushinteger(l, value);
    }
    
    void Push(lua_State* l, float value)
    {
        lua_pushnumber(l, value);
    }
    
    template <typename ... Args>
    void call_snippet(lua_State *L, const std::string& name, Args&&... args) {
        lua_rawgeti(L, LUA_REGISTRYINDEX, snippets[name]);
    
        ((Push(L, std::forward<Args>(args))), ...);
    
        int nargs = sizeof...(Args);
    
        lua_pcall(L, nargs, LUA_MULTRET, 0);
    }
    

    The following complete example should demonstrate this in a similar scenario:

    #include <iostream>
    #include <utility>
    
    void PrintNumber(float f)
    {
        std::cout << f << "(float)\n";
    }
    
    void PrintInt(int i)
    {
        std::cout << i << "(int)\n";
    }
    
    void PrintCstring(char const* str)
    {
        std::cout << str << "(char const*)\n";
    }
    
    void Print(std::nullptr_t) = delete;
    
    void Print(std::string const& str)
    {
        PrintCstring(str.c_str());
    }
    
    void Print(char const* str)
    {
        PrintCstring(str);
    }
    
    void Print(int value)
    {
        PrintInt(value);
    }
    
    void Print(float value)
    {
        PrintNumber(value);
    }
    
    template <typename ... Args>
    void PrintArgs(Args&&... args)
    {
    
        ((Print(std::forward<Args>(args))), ...);
    
        int nargs = sizeof...(Args);
    
        std::cout << "nargs = " << nargs << '\n';
    }
    
    int main()
    {
        PrintArgs("foo", std::string("bar"), 42, 99.9f);
    }
    

    Note: You may need to add some overloads to resolve ambiguity, e.g. when passing 99.9 instead of 99.9f, since the former is a double which results in ambiguity during overload resolution.