Search code examples
pythonc++constructorpybind11pybinding

Pybind11: passing a string* argument to a constructor


In a C++ library that I'm not allowed to change I have a constructor that looks like this:

Dfa(const int n_state, const int dim_alf, const string *alf);

If I simply bind with

.def(py::init<const int, const int, const std::string*>())

it compiles succesfully. The problem is that I can't pass a string* by python, because for example if I try to execute on python

alph=['x','y']
z=Dfa(3,2,alph)

It returns the following error:

TypeError: __init__(): incompatible constructor arguments. The
following argument types are supported:
gi_gipy.Dfa(arg0: int, arg1: int, arg2: unicode)

User "R zu" kindly suggested me to write a wrapper, but I can't figure out how. Given that what in python is something like: ['x','y'] , in c++ is accepted as std::list<std::string> , I tried writing the following code:

.def(py::init([](int n_state,int dim_alf, std::list<std::string> alph){
         std::string* alfabeto=new std::string[dim_alf];
         std::list<string>::iterator it=alph.begin();
         for(int i=0;it!=alph.end();++i,++it)  alfabeto[i]=*it;
         Dfa::Dfa(n_state,dim_alf,alfabeto);
}))

but it returns to me 2 errors:

cannot pass expression of type 'void' to variadic function
construct<Class>(v_h, func(std::forward<Args>(args)...)

and

static_assert failed "pybind11::init(): init function must return a compatible pointer,
  holder, or value"
static_assert(!std::is_same<Class, Class>::value /* always false */

It is clear that I'm a bit confused on how to overcome this problem, that I think is connected to the use of a pointer to string as a parameter to a constructor. I repeat that I can't change the library, I can only create the appropriate binding. Thank you for your attention


Solution

  • main.cpp:

    #include <iostream>
    #include <list>
    #include "pybind11/pybind11.h"
    #include <pybind11/stl.h>
    namespace py = pybind11;
    
    class Dfa{
    public:
        Dfa(const int n_state, const std::size_t size, const std::string* alpha)
                : alpha_(*alpha) {
            std::cout << "n_state: " << n_state << "\n";
            std::cout << "size: " << size << "\n";
            std::cout << "*alpha: " << *alpha << "\n";
        }
        // copy the std::string, not the reference or pointer.
        std::string alpha_; 
    };
    
    Dfa make_dfa(int n_state, std::string alpha) {
        // Copies the python unicode str to a local std::string
        // Modifying the local copy won't change the python
        // str.
        return Dfa(n_state, alpha.size(), &alpha);
        // Problem: Once the program leaves this function,
        // This local std::string is destroyed.
        // If the Dfa class just copies the pointer to this
        // std::string instead of the std::string, the Dfa
        // class will use a destroyed string.
        // Unless the Dfa object copies the string, this will
        // cause big trouble.
    }
    
    void print_char_list(std::list<char> alpha) {
        for (auto c: alpha) std::cout << c << ", ";
        std::cout << "\n";
        std::cout << "length of list is: " << alpha.size() << "\n";
    }
    
    PYBIND11_MODULE(_cpp, m) {
        py::class_<Dfa>(m, "Dfa")
                .def_readwrite("alpha", &Dfa::alpha_);;
        m.def("make_dfa", &make_dfa, "Create a Dfa object");
        m.def("print_char_list", &print_char_list, "Print a list of chars");
    }
    

    CMakeLists.txt:

    cmake_minimum_required(VERSION 3.9)
    project(test_pybind11)
    
    set(CMAKE_CXX_STANDARD 11)
    
    # Find packages.
    set(PYTHON_VERSION 3)
    find_package( PythonInterp ${PYTHON_VERSION} REQUIRED )
    find_package( PythonLibs ${PYTHON_VERSION} REQUIRED )
    
    # Download pybind11
    set(pybind11_url https://github.com/pybind/pybind11/archive/stable.zip)
    
    set(downloaded_file ${CMAKE_BINARY_DIR}/pybind11-stable.zip)
    file(DOWNLOAD ${pybind11_url} ${downloaded_file})
    execute_process(COMMAND ${CMAKE_COMMAND} -E tar xzf ${downloaded_file}
            SHOW_PROGRESS)
    file(REMOVE ${downloaded_file})
    
    set(pybind11_dir ${CMAKE_BINARY_DIR}/pybind11-stable)
    add_subdirectory(${pybind11_dir})
    include_directories(${pybind11_dir}/include)
    
    # Make python module
    pybind11_add_module(_cpp main.cpp)
    

    Python 3 test:

    >>> import _cpp
    >>> s = "xyz"
    >>> d = _cpp.make_dfa(1, s)
    n_state: 1
    size: 3
    *alpha: xyz
    >>> print(d.alpha)
    xyz
    >>> d.alpha = "abc"
    >>> d.alpha
    'abc'
    >>> _cpp.print_char_list(['x', 'y', 'z'])
    x, y, z, 
    length of list is: 3