samedi 20 février 2021

Boost.Python non-const vs const Method Arguments

I have been trying to get my C++ code to be wrapped by Boost::Python. The code I want is below and has been simplified. None of the arguments in BoundaryCheck are const and when I call it from python I get the error:

Procedures.BoundaryCheck(Procedures, dict, str, list, int) did not match C++ signature

When I change my prototype and wrapper to:

std::vector<std::vector<std::string> > BoundaryCheck(const std::map<std::string,std::string> &qcAttr, const std::string &targetSensor, const std::vector<std::vector<std::string> > &qcData, int verbose = 0) const;

.def("BoundaryCheck", (::std::vector<std::vector<std::string> > (::Products::Procedures::*)(::std::map< std::string, std::string > const &, std::string const &, std::vector<std::vector<std::string> > const &, int))(&::Products::Procedures::BoundaryCheck), BoundaryCheck_overloads(boost::python::args("qcAttr", "targetSensor", "qcData", "verbose")));

Things work as expected (I also changed Procedures.cpp but it should be obvious how). Its not entirely obvious to me why changing the method arguments to const causes things to work correctly. Changing all the arguments to const would be fine except the qcData argument since it is changed and returned by the method. Why does const cause this to work? What can I do to make this work without changing qcData to const?

Procedures.h

#ifndef PROCEDURES_H_
#define PROCEDURES_H_

#include <string>
#include <vector>
#include <map>

namespace Products
{
  class Procedures
  {
  public:
      std::vector<std::vector<std::string> > BoundaryCheck(std::map<std::string,std::string> &qcAttr, std::string &targetSensor, std::vector<std::vector<std::string> > &qcData, int verbose = 0);
  }
}

#endif

Procedures.cpp

#include "Procedures.h"
#include <map>
#include <string>
#include <vector>

std::vector<std::vector<std::string> > Products::Procedures::BoundaryCheck(std::map<std::string,std::string> &qcAttr, std::string &targetSensor, std::vector<std::vector<std::string> > &qcData, int verbose)
{
  qcData[0][0] = "test";
  return qcData;
}

wrapper.cpp

#include <boost/python.hpp>
#include <boost/python/overloads.hpp>
#include <boost/python/args.hpp>
#include "/data/alucard/include/Procedures.h"

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(BoundaryCheck_overloads, Products::Procedures::BoundaryCheck, 3, 4)

struct iterable_converter
{
  template <typename Container> iterable_converter &from_python()
  {
    boost::python::converter::registry::push_back(&iterable_converter::convertible, &iterable_converter::construct<Container>, boost::python::type_id<Container>());
    return *this;
  }

  static void* convertible(PyObject* object)
  {
    return PyObject_GetIter(object) ? object : NULL;
  }

  template <typename Container> static void construct(PyObject* object, boost::python::converter::rvalue_from_python_stage1_data* data)
  {
    std::vector< std::vector< std::string>> vecvec;
    std::vector< std::string> vec;
    PyObject *objectIN;
    PyObject *value;
    Py_ssize_t pos1 = PyList_Size(object);

    boost::python::handle<> handle(boost::python::borrowed(object));
    typedef boost::python::converter::rvalue_from_python_storage<Container> storage_type;
    void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
    typedef boost::python::stl_input_iterator<typename Container::value_type> iterator;

    new (storage) Container(iterator(boost::python::object(handle)), iterator());
    data->convertible = storage;
  }
};

struct pydict_converter
{
  template <typename Container> pydict_converter &from_python()
  {
    boost::python::converter::registry::push_back(&pydict_converter::convertible, &pydict_converter::construct<Container>, boost::python::type_id<Container>());
    return *this;
  }

  static void* convertible(PyObject* object)
  {
    return PyDict_Check(object) ? object : NULL;
  }

  template <typename Container> static void construct(PyObject* object, boost::python::converter::rvalue_from_python_stage1_data* data)
  {
    PyObject *key, *value;
    Py_ssize_t pos = 0;
    std::map<std::string,std::string> map;

    boost::python::handle<> handle(boost::python::borrowed(object));
    typedef boost::python::converter::rvalue_from_python_storage<Container> storage_type;
    void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;

    while (PyDict_Next(object, &pos, &key, &value))
      {
    char * k = PyUnicode_AsUTF8(key);
    char * v = PyUnicode_AsUTF8(value);

    map.insert(std::map<std::string,std::string>::value_type(k,v));
      }

    data->convertible = new (storage) Container(map);
  }
};

struct vecvec_converter
{
  static PyObject* convert(const std::vector< std::vector< std::string>> &vecvec)
  {
    PyObject* list = PyList_New(0);
    PyObject* mval;

    for (auto const& y : vecvec)
      {
    PyObject* templist = PyList_New(0);
    for (auto const& x : y)
      {
        mval = PyBytes_FromString(&(x)[0]);
        PyList_Append(templist, mval);
      }
    PyList_Append(list,templist);
      }
    return list;
  }
};

BOOST_PYTHON_MODULE(procedures)
{
  pydict_converter()
    .from_python<std::map<std::string,std::string> >();

  iterable_converter()
    .from_python<std::vector<std::vector<std::string> > >();

  iterable_converter()
    .from_python<std::vector<std::string> >();

  boost::python::to_python_converter<std::vector<std::map<std::string,std::string>>, vecmap_converter>();

  //vector<vector> -> list(list)
  boost::python::to_python_converter<std::vector<std::vector<std::string>>, vecvec_converter>();

  boost::python::class_<Products::Procedures, boost::noncopyable>("Procedures")
    .def("BoundaryCheck", (::std::vector<std::vector<std::string> > (::Products::Procedures::*)(::std::map< std::string, std::string > &, std::string &, std::vector<std::vector<std::string> > &, int))(&::Products::Procedures::BoundaryCheck), BoundaryCheck_overloads(boost::python::args("qcAttr", "targetSensor", "qcData", "verbose")));
}

wrapper.py

import procedures

proc = procedures.Procedures()
qcAttr = {"m_sensorLoc_row":"0", "m_qcboundstable":"qcbounds", "m_sensortable":"sensor", "m_unitLoc_row":"1", "m_dataLoc_row":"2", "m_flagtag":"_SCQC_Flag"}
qcData = [["Timestamp","AirTemp2m","AirTemp2m_SCQC_Flag"],["TS","C","Flag"],["2020-01-01 00:00:00+00","-100.99",""]]
data = proc.BoundaryCheck(qcAttr, "AirTemp2m", qcData, 1)

Aucun commentaire:

Enregistrer un commentaire