Home > Software engineering >  How to iterate over a parameter pack and switch on parameter type?
How to iterate over a parameter pack and switch on parameter type?

Time:10-13

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?

CodePudding user response:

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.

CodePudding user response:

In , using template lambda and fold expression (since ), you might do as follows:

template <typename... Args>
void call_snippet(lua_State* L, const std::string& name, Args&&... args)
{
    std::size_t nargs{ 0u }; // or just sizeof...(Args) ??

    const auto call_with_x = [&]<typename T>(T&& x)
    {
        if constexpr (std::is_same_v<int, T>)
        {
            // ... lua_pushinteger(L, std::forward<T>(x));
        }
        else if constexpr (std::is_same_v<float, T>)
        {
            // ... lua_pushnumber(L, std::forward<T>(x));
        }
        else if constexpr (std::is_same_v<std::string, T>)
        {
            // ... lua_pushcstring(L, std::forward<T>(x).c_str());
        }
        // ... so on ...
        else
        {
            // ... do something by default!
        }
    };

    // call lambda "call_with_x"
    ((call_with_x(std::forward<Args>(args))),...);

}

(See a Demo)


Alternatively, you could also use generic lambdas (Since ) and get the type of x by std::decay_t<decltype(x)>.


template <typename... Args>
void call_snippet(lua_State* L, const std::string& name, Args&&... args)
{
    const std::size_t nargs{ sizeof...(Args) }; // just ??

    const auto call_with_x = [&](auto&& x)
    {
        using T = std::decay_t<decltype(x)> ;
        // ... as before
    };

    // call lambda "call_with_x"
    ((call_with_x(std::forward<Args>(args))), ...);
    std::cout << "Total args: " << nargs;

}

(See live demo)

  • Related