Using c++11 lambda as accessor function in boost::

2019-04-27 22:25发布

I am trying to use c++11 lambdas as accessor functions in boost::python's add_property, something along the following (the lambda is not strictly needed in this example, but will be needed for more complicated things happening inside the lambda, such as validation):

#include<boost/python.hpp>

struct A{
  A(): a(2){};
  int a;
};

BOOST_PYTHON_MODULE(boost_python_lambda)
{
  boost::python::class_<A>("A")
    // .def_readonly("a",&A::a) // the classical way: works fine 
    .add_property("a",[](const A& a){return a.a;})
  ;
}

However, compiling with clang++ (ver. 3.2) and -std=c++11 (the result is the same with g++ 4.7), I get this error:

/usr/include/boost/python/class.hpp:442:66: error: no matching function for call to 'get_signature'
        return python::make_function(f, default_call_policies(), detail::get_signature(f, (T*)0));
                                                                 ^~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/python/class.hpp:422:22: note: in instantiation of function template specialization 'boost::python::class_<A,
      boost::python::detail::not_specified, boost::python::detail::not_specified,
      boost::python::detail::not_specified>::make_fn_impl<A, <lambda at boost_python_lambda.cpp:12:21> >' requested here
        return this->make_fn_impl(
                     ^
/usr/include/boost/python/class.hpp:309:40: note: in instantiation of function template specialization 'boost::python::class_<A,
      boost::python::detail::not_specified, boost::python::detail::not_specified,
      boost::python::detail::not_specified>::make_getter<<lambda at boost_python_lambda.cpp:12:21> >' requested here
        base::add_property(name, this->make_getter(fget), docstr);
                                       ^
boost_python_lambda.cpp:12:4: note: in instantiation of function template specialization 'boost::python::class_<A,
      boost::python::detail::not_specified, boost::python::detail::not_specified,
      boost::python::detail::not_specified>::add_property<<lambda at boost_python_lambda.cpp:12:21> >' requested here
                .add_property("a",[](const A& a){return a.a;})
                 ^

I tried wrapping the lambda in std::function<int(const A&)>(...), but that did not help with the argument deduction. Any idea?

4条回答
贪生不怕死
2楼-- · 2019-04-27 23:07

Use the make_function() function to create Python callable objects. If Boost.Python cannot deduce the function object signature, then the signature must be explicitly provided as an MPL front-extensible sequence. For example, the lambda [](const A& a) { return a.a; } returns an int and accepts const A&, so one could use boost::mpl::vector<int, const A&>() for the signature.

Here is a complete example demonstrating using a pointer-to-data-member, casting non-capturing lambda to a function, and using a lambda/functor:

#include <boost/python.hpp>

struct A
{
  A(): a(2) {};
  int a;
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<A>("A")
    // Expose pointer-to-data-member.
    .add_property("a1", &A::a)
    // Cast non-capturing lambda to a function.
    .add_property("a2", +[](const A& a) { return a.a; })
    // Make a python function from a functor.
    .add_property("a3", python::make_function(
        [](const A& a) { return a.a; },
        python::default_call_policies(),
        boost::mpl::vector<int, const A&>()))
    ;
}

Interactive usage:

>>> import example
>>> a = example.A()
>>> assert(a.a1 == 2)
>>> assert(a.a2 == 2)
>>> assert(a.a3 == 2)

An alternative non-intrusive approach that is based on documented behavior is to write the lambda as a non-member function, then exposing it as the fget argument. While not as succinct as lambda, it still allows for additional functionality, such as validation, to occur when accessing the member variable.

#include <boost/python.hpp>

struct A{
  A(): a(2){};
  int a;
};

int get_a(const A& a)
{
  // do validation
  // do more complicated things
  return a.a;
}

BOOST_PYTHON_MODULE(example)
{
  boost::python::class_<A>("A")
    .add_property("a", &get_a);
  ;
}
查看更多
祖国的老花朵
3楼-- · 2019-04-27 23:08

If you make the function type explicit by creating an std::function, then using the following piece of (C++11) code you can do it

namespace boost {
  namespace python {
    namespace detail {

      template <class T, class... Args>
      inline boost::mpl::vector<T, Args...> 
        get_signature(std::function<T(Args...)>, void* = 0)
      {
        return boost::mpl::vector<T, Args...>();
      }

    }
  }
}

example:

boost::python::class_<A>("A")
    // .def_readonly("a",&A::a) // the classical way: works fine 
    // .add_property("a", [](const A& a){return a.a; }) // ideal way, not possible since compiler cannot deduce return / arguments types
    .add_property("a", std::function<int(const A&)>([](const A& a){return a.a; }))
    ;
查看更多
家丑人穷心不美
4楼-- · 2019-04-27 23:22

Hopping in here two years later, Boost.Python indeed does not support wrapping function objects. But your lambda does not capture anything. As such, it can be explicitly convertible to a function pointer:

BOOST_PYTHON_MODULE(boost_python_lambda)
{
  boost::python::class_<A>("A")
    // .def_readonly("a",&A::a) // the classical way: works fine 
    .add_property("a", +[](const A& a){return a.a;})
                       ↑↑↑
  ;
}

All you need is that +.

查看更多
Bombasti
5楼-- · 2019-04-27 23:28

I've encountered the same problem when trying to use a lambda, and based on the solution for std::function above, I've added some more template magic, deducing the type of the operator() member function of the lambda (or functor):

https://gist.github.com/YannickJadoul/013d3adbf7f4e4abb4d5

And then things like this just work, when this header is included:

int *x = new int(0);
def("inc", [x] () { ++*x; });
def("get", [x] () { return *x; });

The only caveat that I know of, ftm, is that you should include this header befóre including boost/python.hpp (or any other Boost.Python header you are using that will need the get_signature function declarations):

#include "functor_signature.h"
#include <boost/python.hpp>
查看更多
登录 后发表回答