SWIG: Passing a list as a vector<double> pointer to a constructor

Question:

Trying to use swig to pass a python list as input for c++ class with a (one of many) constructor taking a std::vector<double> * as input. Changing the C++ implementation of the codebase is not possible.

<EDIT> : What I am looking for is a way to "automatically" process a python list to a vector<double> * or say for example:

fake_class.cpp

class FakeClass
{
public:
   std::vector<double>* m_v;
   FakeClass(std::vector<double>* v) : m_v {v} {}
   void SomeFunction(); // A function doing some with said pointer (m_v)
   [...]
};

and then using this in python (say the compiled extension is fakeExample:

import fakeExample as ex
a = ex.FakeClass([1.2, 3.1, 4.1])
print(a.m_v)
a.SomeFunction()

without crashing. </EDIT>


What I’ve tried:

Code:

example.hpp

#include <vector>
#include <iostream>


class SampleClass
{
public:
    std::vector<double>* m_v;
    SampleClass(std::vector<double>* v) : m_v {v} {
     std::cout << "nnon default constructor!n";}

    SampleClass() {std::cout << "default constructor!n";}
    void SampleMethod(std::vector<double>* arg);
    void SampleMethod2(std::vector<double>* arg);
    void print_member();
};

example.cpp

#include "example.hpp"
#include <iostream>


void SampleClass::SampleMethod(std::vector<double>* arg)
{
    for (auto x : (*arg)) std::cout << x << " ";

};

void SampleClass::SampleMethod2(std::vector<double>* arg)
{
    auto vr = arg;
    for (size_t i = 0; i < (*vr).size(); i++)
    {
        (*vr)[i] += 1;
    }
    for (auto x : (*vr)) std::cout<< x << "n";
}

void SampleClass::print_member() {
    for (auto x : (*m_v)) {
        std::cout << x << " ";
    }
}

example.i

%module example

%{
    #include "example.hpp"
%}

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


%template(doublevector) std::vector<double>;

/* NOTE: Is this required? */
%naturalvar Sampleclass;


/* NOTE: This mostly works but not for constructor */
%apply std::vector<double> *INPUT {std::vector<double>* };

%include "example.hpp"

A Makefile (s_test.py is a simple test script I am not including here).

all: clean build run

clean:
        rm -rf *.o *_wrap.* *.so __pycache__/ *.gch example.py

build:
        swig -python -c++ example.i
        g++ -c -fPIC example.cpp example_wrap.cxx example.hpp -I/usr/include/python3.8
        g++ -shared example.o example_wrap.o -o _example.so
run:
        python s_test.py

build_cpp:
        g++ example.cpp example.hpp main.cpp -o test_this.o

And finally after compiling etc :

>>> import example as ex
>>> a = ex.SampleClass()
default constructor!
>>> a.SampleMethod([1.2, 3.1, 4.1])
1.2 3.1 4.1
>>> a.SampleMethod2([3.1, 2.1])
4.1
3.1    # Works fine(or at least as expected) until here.
>>> b = ex.SampleClass([1.2]) 
                                                                                                                                                                                                                     non default constructor!
>>> b.m_v
<example.doublevector; proxy of <Swig Object of type 'std::vector< double > *' at SOME_ADDRESS> >
>>> b.m_v.size()
17958553321119670438
>>> b.print_member()
>>> [... Lots of zeroes here ...]0 0 0 0[1]    <some_number> segmentation fault  python

And exits.

Thank you 🙂

Asked By: LateBird

||

Answers:

The Python list passed into the non-default constructor gets converted to a temporary SWIG proxy of a vector<double>* and that pointer is saved by the constructor into SampleClass’s m_v member, but the pointer no longer exists when the constructor returns. If you create a persistent doublevector and make sure it stays in scope, the code works.

I built the original code in the question for the following example:

>>> import example as ex
>>> v = ex.doublevector([1,2,3,4])
>>> s = ex.SampleClass(v)

non default constructor!
>>> s.m_v.size()
4
>>> list(s.m_v)
[1.0, 2.0, 3.0, 4.0]

As long as v exists, s.m_v will be valid.

Answered By: Mark Tolonen

Just in case anyone is looking for another way to solve this, what I ended up doing was extending the class with a new constructor like this (inspired by this answer):

%extend SampleClass {
    SampleClass(std::vector<double>* vec) {
        auto tmp_buff = new std::vector<double>;
        for (auto x : (*vec)) tmp_buff->push_back(x);
        return new SampleClass(tmp_buff);
}
};

This still throws a redefinition warning which I am not sure how to solve.

Then in python:

>>> import example as ex
>>> a = ex.SampleClass([1.2, 3.1, 2.2])
>>> a.print_member()
1.2 3.1 2.2 
>>>
Answered By: LateBird
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.