pythonswigswig-typemap

vector of enum not correctly handled by SWIG


Dears, I use SWIG to generate Python bindings to a C++ API (and it works great!) but I have serious difficulty wrapping a function that takes a vector of enum as argument. I have built a minimal example to simplify the debugging, which I put as an attachment to this issue. It seems to me that the example should work, at least it works well for a vector of integer argument.

The need is pretty simple : we have a C++ method with the following signature: void Run(const std::vector<double> & in, std::vector<int> & out, std::vector<testing::Status> & status) where testing::Status is an enum and we will like to obtain a Python method like: out, status = Run(in)

Using the attached example, the swig executable does not raise any error, and the Python Run method can be ran, but the output value status cannot be used, an error is raised:

status: (<Swig Object of type 'testing::Status *' at 0x7fa441156450>, <Swig Object of type 'testing::Status *' at 0x7fa441156660>) swig/python detected a memory leak of type 'testing::Status *', no destructor found. swig/python detected a memory leak of type 'testing::Status *', no destructor found.

Here are the different files that can be used to reproduce the error:


mylib.h, the C++ to wrap in Python

#include <vector>

namespace testing
{
    typedef enum
    {
        Ok = 0,
        Error = 1,
    } Status;
    
    class Algo
    {

    public:
        void Run(const std::vector<double> & in, std::vector<int> & out, std::vector<testing::Status> & status)
        {
            status.resize(in.size());
            out.resize(in.size());
            for (int i=0; i<in.size(); ++i) {
                out[i] = i;
                status[i] = Status::Ok;
            }
        }
    };
}

mymodule.i, the SWIG interface file

%module mymodule

%{
    #include "mylib.h"
%}

%include "std_vector.i"
%include "typemaps.i"

%define STD_TEMPLATE(TYPE...)
    %template() TYPE;
    %apply TYPE& OUTPUT {TYPE&}
    %typemap(argout) const TYPE& {
        // do nothing for const references
    }
    %typemap(out) (TYPE&) = (const TYPE&);
%enddef

STD_TEMPLATE (std::vector <int>);
STD_TEMPLATE (std::vector <double>);
STD_TEMPLATE (std::vector < testing::Status >);

%include "mylib.h"

build.sh, the build command line used to compile binaries

${swig_install}/bin/swig \
    -I. \
    -I${swig_install}/share/swig/${swig_version}/python \
    -I${swig_install}/share/swig/${swig_version} \
    -c++ -python \
    -outdir . \
    -o "mymodule.cxx" \
    "mymodule.i"

g++ -L${python_install}/lib -lpython3 \
    -I${python_install}/include/python \
    -I. \
    -std=c++11 -shared -fPIC \
    mymodule.cxx -o _mymodule.so

run.py, the example in Python that raises the error

import mymodule as mm

algo = mm.Algo()

out, status = algo.Run([1.1, 2.2])
print("out:", out)
print("status:", status)

Solution

  • SWIG doesn't know what to do with the vector of enum outputs. One way is to handle the typemaps yourself:

    mylib.i

    %module mylib
    
    %{
        #include "mylib.h"
    %}
    
    %include "std_vector.i"
    %include "typemaps.i"
    
    %define STD_TEMPLATE(TYPE...)
        %template() TYPE;
        %apply TYPE& OUTPUT {TYPE&}
        %typemap(argout) const TYPE& {
            // do nothing for const references
        }
        %typemap(out) (TYPE&) = (const TYPE&);
    %enddef
    
    STD_TEMPLATE (std::vector <int>);
    STD_TEMPLATE (std::vector <double>);
    
    // Don't require an input parameter in Python.
    // Create a temporary vector to hold the output result.
    %typemap(in,numinputs=0) std::vector<testing::Status>& (std::vector<testing::Status> tmp) %{
        $1 = &tmp;
    %}
    
    // Create a Python list object the same size as the vector
    // and copy and convert the vector contents into it.
    %typemap(argout) std::vector<testing::Status>& (PyObject* list) %{
        list = PyList_New($1->size());
        for(int x = 0; x < $1->size(); ++x)
            PyList_SET_ITEM(list, x, PyLong_FromLong($1->at(x)));
        $result = SWIG_Python_AppendOutput($result, list);
    %}
    
    %include "mylib.h"
    

    Output (same mylib.h and run.py):

    out: (0, 1)
    status: [0, 0]