5

I have a C++ function that returns a list of structs. Inside the struct, there are more lists of structs.

struct CameraInfo {
    CamName                     name;
    std::list<CamImageFormat>  lImgFormats;
    std::list<CamControls>      lCamControls;
};

std::list<CameraInfo> getCameraInfo()
{
    std::list<CameraInfo> lCamerasInfo;
    // fill lCamerasInfo
    return lCamerasInfo; 
}

then for exporting it I was using:

class_<CameraNode....> >("CameraNode", no_init)
...
...
.def("listCameraInfo", make_function(&CameraNode::listCameraInfo))
        .staticmethod("listCameraInfo")
...
;

And it was OK since I was using cout to print the data on screen... I would like now to use the return value and it's content from python like properties, this way:

cameras = []
cameras = CameraNode.getCameraInfo()
print cameras[0].name
print cameras[0].lImgFormats[0]
and so on...

Is this even possible?? Should I be using add_property instead? I don't think I can create a class for every struct. This design made sense while I was working on C++ only but now that I would have to wrap it, I'm getting more and more confused.

Any advice on wrapping std::list with boost.python in a general way would be very well accepted.

Edit:

I will add here links that I've found useful: Iterators StlContainers

Community
  • 1
  • 1
Robert Parcus
  • 1,029
  • 13
  • 19
  • Your Python snippet wouldn't work. If you want to add `CameraNode.getCameraInfo()` to the `cameras` list do `cameras.append(CameraNode.getCameraInfo())`. If you just want the info, just do `camera = CameraNode.getCameraInfo()`, and access it like `print camera.name`. – agf Jul 21 '11 at 14:11
  • tnx @agf but there is more missing... If I do as you say I get no error message, but I also have cameras filled with `None`, which is not what I would want :) In the mean time I've found this: [stlContainers](http://wiki.python.org/moin/boost.python/StlContainers) – Robert Parcus Jul 21 '11 at 14:20
  • I know it didn't answer your question, that's why I posted it as a comment. – agf Jul 21 '11 at 14:22

2 Answers2

13

Does it have to be std::list ? If you use std::vector instead you can use boost::python::vector_indexing_suite to wrap the list. See this post for details.

If you must use std::list you'll need to create a helper class that wraps the std::list functionality with python's list methods. That can be quite involved, but doable.

std_item.hpp:

#include <list>
#include <algorithm>
#include <boost/python.hpp>

template<class T>
struct listwrap
{
    typedef typename T::value_type value_type;
    typedef typename T::iterator iter_type;

    static void add(T & x, value_type const& v)
    {
        x.push_back(v);
    }

    static bool in(T const& x, value_type const& v)
    {
        return std::find(x.begin(), x.end(), v) != x.end();
    }

    static int index(T const& x, value_type const& v)
    {
        int i = 0;
        for(T::const_iterator it=x.begin(); it!=x.end(); ++it,++i)
            if( *it == v ) return i;

        PyErr_SetString(PyExc_ValueError, "Value not in the list");
        throw boost::python::error_already_set();
    }

    static void del(T& x, int i)
    {
        if( i<0 ) 
            i += x.size();

        iter_type it = x.begin();
        for (int pos = 0; pos < i; ++pos)
            ++it;

        if( i >= 0 && i < (int)x.size() ) {
            x.erase(it);
        } else {
            PyErr_SetString(PyExc_IndexError, "Index out of range");
            boost::python::throw_error_already_set();
        }
    }

    static value_type& get(T& x, int i)
    {
        if( i < 0 ) 
            i += x.size();

        if( i >= 0 && i < (int)x.size() ) {
            iter_type it = x.begin(); 
            for(int pos = 0; pos < i; ++pos)
                ++it;
            return *it;                             
        } else {
            PyErr_SetString(PyExc_IndexError, "Index out of range");
            throw boost::python::error_already_set();
        }
    }

    static void set(T& x, int i, value_type const& v)
    {
        if( i < 0 ) 
            i += x.size();

        if( i >= 0 && i < (int)x.size() ) {
            iter_type it = x.begin(); 
            for(int pos = 0; pos < i; ++pos)
                ++it;
            *it = v;
        } else {
            PyErr_SetString(PyExc_IndexError, "Index out of range");
            boost::python::throw_error_already_set();
        }
    }
};


template<class T>
void export_STLList(const char* typeName)
{
    using namespace boost::python;

    class_<std::list<T> >(typeName)
        .def("__len__", &std::list<T>::size)
        .def("clear", &std::list<T>::clear)
        .def("append", &listwrap<T>::add,
            with_custodian_and_ward<1,2>()) // to let container keep value
        .def("__getitem__", &listwrap<T>::get,
            return_value_policy<copy_non_const_reference>())
        .def("__setitem__", &listwrap<T>::set,
            with_custodian_and_ward<1,2>()) // to let container keep value
        .def("__delitem__", &listwrap<T>::del)
        .def("__contains__", &listwrap<T>::in)
        .def("__iter__", iterator<std::list<T> >())
        .def("index", &listwrap<T>::index);
}

usage:

typedef std::list<int> intlist;
export_STLList<int>("intlist");
Community
  • 1
  • 1
Aleksey Vitebskiy
  • 2,087
  • 1
  • 15
  • 14
  • Great example @Aleksey:) I had to `typedef typename T::const_iterator` on `index()` due to [compiler](http://stackoverflow.com/questions/642229/why-do-i-need-to-use-typedef-typename-in-g-but-not-vs) issues. I still have this error tough.. `WrapHelper.cpp: In function ‘void export_STLList(const char*)’: WrapHelper.cpp:405:54: error: wrong number of template arguments (1, should be 5) /usr/include/c++/4.5/bits/stl_iterator_base_types.h:114:12: error: provided for ‘template struct std::iterator’ ` – Robert Parcus Jul 22 '11 at 09:57
  • sorry, fixed. It should be .def("__iter__", iterator >()) instead of .def("__iter__", std::iterator >()) – Aleksey Vitebskiy Jul 22 '11 at 12:13
0

If one-way (from c++ to python) wrapping is enough, then you can define a direct converter from list<list<YourClass> > -- see my vector<vector<string> > converter -- just change types as you need, and don't forget to register the converter.

You could also have a method returning python::list (which would itself contain python::list's with your objects) which would iterate over c++ nested list and build native python list out of it, but it would only work in one case you have.

For two-way conversions, either have a look at my file (which contains both-way converters for different types) -- advantage being you get native python lists, disadvatage is copying the objects. For two-way conversion of large collections, indexing_suite is definitely the way to go.

There is indexing_suite_v2, which is allegedly much better, including direct support for std::list and std::map, though unfortunately quite badly documented (last time I looked, about 1.5 years ago) and not official part of boost::python.

eudoxos
  • 18,545
  • 10
  • 61
  • 110
  • do you by any chance have a link to indexing_suite_v2? – Aleksey Vitebskiy Jul 22 '11 at 15:58
  • It is a part of [py++](http://sourceforge.net/projects/pygccxml/files/pyplusplus/pyplusplus-1.0/), though contrary to few years back, it does not seem to be very active and the website language-binding.net where it used to be hosted before does not exist anymore :-| – eudoxos Jul 24 '11 at 07:50