c++luaswigtypemaps

Creating a SWIG typemap for function that returns vector of pairs


I'm trying to create a getter function that returns a vector of pairs in Lua.

I have the following vector data in C++:

{{1, "a"}, {2, "b"}, {3, "c"}}

I want to return this vector as table in Lua so it can be same as the following table t in Lua:

local t = {};
t[1].value = 1
t[1].name = "a"
t[2].value = 2
t[2].name = "b"
t[3].value = 3
t[3].name = "c"

Here's my code :

"MyBindings.h"

#include "main.h"

class MyClass
{
public:
    MyClass()
    :MyData({{1, "a"}, {2, "b"}, {3, "c"}}){}

    void getMyData(std::vector<pair<float, std::string>> *datap)
    {
        *datap = MyData;
    }
    std::vector<pair<float, std::string>> MyData;
};

"MyBindings.i"

%module my
%{
    #include "MyBindings.h"
%}

%include <stl.i>
%include <typemaps.i>
%include <std_string.i>
%include <std_vector.i>

/* convert the output std::vector<pair<float, std::string>> to lua_Table */
%typemap(in, numinputs = 0) (std::vector<pair<float, std::string>> *datap) 
(std::vector<pair<float, std::string>> *tdatap = nullptr) 
%{
%}
%typemap(argout) (std::vector<pair<float, std::string>> *datap) 
{
    lua_newtable(L);
    for (size_t i = 0; i < $1->size(); ++i)
    {
        lua_newtable(L);
        lua_pushinteger(L, static_cast<lua_Number>($1->at(i).first));
        lua_setfield(L, -2, "value");
        lua_pushstring(L, $1->at(i).second.c_str());
        lua_setfield(L, -2, "name");
        lua_rawseti(L, -2, i + 1);
    }
    SWIG_arg++;
}

%include "MyBindings.h"

"main.cpp"

#include "main.h"
#include "lua.hpp"

extern "C"
{
    int luaopen_my(lua_State *L);
}

int main()
{
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    luaopen_my(L);
    lua_settop(L, 0);
    luaL_dostring(L, "local c = my.MyClass()\n"
                     "local t = c:getMyData()\n"
                     "print('Value : ' .. t[2].value)\n"
                     "print('Name : ' .. t[2].name)\n");
    lua_close(L);
}

The Result I Want :

Value : 2
Name : b

The Result I Get :

vector: Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)

How should I change my code to get the result I want?


Solution

  • Basically the solution is to not use output arguments (argout). This is actually a general advice for C++ programming. Output arguments were necessary in the era of C when you couldn't easily return arrays from a function by value.

    After a little bit of refactoring I end up with the following:

    MyBindings.h

    #include <string>
    #include <utility>
    #include <vector>
    
    class MyClass {
        std::vector<std::pair<float, std::string>> m_data;
    public:
        MyClass() : m_data({{1, "a"}, {2, "b"}, {3, "c"}}) {}
    
        std::vector<std::pair<float, std::string>> data() { return m_data; }
    };
    

    MyBindings.i

    %module my
    %{
        #include "MyBindings.h"
    %}
    
    %typemap(out) std::vector<std::pair<float, std::string>>
    {
        lua_newtable(L);
        for (size_t i = 0; i < $1.size(); ++i)
        {
            lua_newtable(L);
            lua_pushinteger(L, static_cast<lua_Number>($1.at(i).first));
            lua_setfield(L, -2, "value");
            lua_pushstring(L, $1.at(i).second.c_str());
            lua_setfield(L, -2, "name");
            lua_rawseti(L, -2, i + 1);
        }
        SWIG_arg++;
    }
    
    %include "MyBindings.h"
    

    main.cpp

    #include "lua.hpp"
    
    extern "C"
    {
        int luaopen_my(lua_State *L);
    }
    
    int main()
    {
        lua_State *L = luaL_newstate();
        luaL_openlibs(L);
        luaopen_my(L);
        lua_settop(L, 0);
        luaL_dostring(L, "local c = my.MyClass()\n"
                         "local t = c:data()\n"
                         "print('Value : ' .. t[2].value)\n"
                         "print('Name : ' .. t[2].name)\n");
        lua_close(L);
    }
    

    Example invocation:

    $ swig -c++ -lua MyBindings.i
    $ clang++ -Wall -Wextra -Wpedantic -std=c++11 -I/usr/include/lua5.3 MyBindings_wrap.cxx main.cpp -llua5.3
    $ ./a.out 
    Value : 2
    Name : b