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

  • 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. – hackela May 10 '18 at 18:44
  • Unfortunately I cannot switch to char[] since I cannot change the library as it is implemented –  May 10 '18 at 18:58
  • You can use the function c_str() and string() to do the cast before calling this lib function – hackela May 10 '18 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'])` ? –  May 10 '18 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 – hackela May 10 '18 at 19:19
  • But the problem is that I have a pointer to strings, not a string –  May 10 '18 at 20:04
  • I didn't realized that. And something like that: ```Dfa_str(int a, int b, string c)``` calling ```Dfa(a, b, &c)``` – hackela May 10 '18 at 20:50
  • This constructor takes as input a pointer to string because it creates an object that "speak is own language", so it has to recognize the symbols of his language, that might be entire strings like "foo" or whatever else. Unfortunately I don't understand your suggestion. Because I have to bind with that constructor on the first post, but I have to find a way to intercept from python a list. I don't know if my problem is really clear –  May 11 '18 at 07:00

1 Answers1

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
R zu
  • 2,034
  • 12
  • 30
  • 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. – R zu May 12 '18 at 20:37
  • 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 –  May 12 '18 at 22:11
  • 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. – R zu May 12 '18 at 22:12
  • 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! –  May 12 '18 at 22:12
  • I did not included your sample code for Dfa(...), I'm correctly calling the constructor from my library, that's not the problem –  May 12 '18 at 22:16