3

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

8
  • I'm not sure if what you call a string is the same in python and c++ (std). It's a complete structure with hidden parameters. Maybe switching to char[] will help you and avoid this definition difference. Commented May 10, 2018 at 18:44
  • Unfortunately I cannot switch to char[] since I cannot change the library as it is implemented Commented May 10, 2018 at 18:58
  • You can use the function c_str() and string() to do the cast before calling this lib function Commented May 10, 2018 at 18:59
  • How do I intercept this in c++ side? My doubt is, I have the constructor in c++ specified above, what binding .def do I have to write in order to bind that correct constructor and intercept in python a call like x=Dfa(3,2,['x','y']) ? Commented May 10, 2018 at 19:12
  • Can you just do a wrapping function in between them with something like: Dfa_char(int a, int b, *char c) calling Dfa(a, b, std::string(c)). And you can use this Dfa_char to import whatever you have in your python code Commented May 10, 2018 at 19:19

1 Answer 1

1

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
Sign up to request clarification or add additional context in comments.

5 Comments

I really think the constructor of Dfa should be Dfa(const int state, const std::string& alpha). There is no need for passing the length of the string to constructor of Dfa because a std::string already contains this length.
I tried with void dfa_wrapper(int n_state, std::string alpha) { // Copy the python unicode string // and make a c++ std::string. // Modifying this copy won't change // the original python string. std::string* alph= new string[alpha.size()]; for(int i=0;i<alpha.size();++i) alph[i]=alpha[i]; Dfa::Dfa(n_state, alpha.size(), alph); } and binding m.def("Dfa", &dfa_wrapper, "Wrapper of your Dfa::dfa"); but it returns the error ImportError: generic_type: cannot initialize type "Dfa": an object with that name is already defined
Well. the class Dfa I made is a demo of the Dfa in your library because I don't know what is in it. Replace the Dfa I make with the Dfa in the library.
I think it might be caused by the fact that I have multiple constructor named Dfa, for example I have the default empty constructor Dfa() which is binded by .def(py::init(),"Make an instance of null dfa")always thank for your help!
I did not included your sample code for Dfa(...), I'm correctly calling the constructor from my library, that's not the problem

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.