9

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?

Praetorian
  • 106,671
  • 19
  • 240
  • 328
eudoxos
  • 18,545
  • 10
  • 61
  • 110
  • Did you try `add_property("a",[](const A& a){return a.a;})`? It looks like `boost::python` cannot figure out the function signature. – Jesse Good May 30 '13 at 21:29
  • I'm guessing that `get_signature` only checks for normal and member function pointers, not function objects' `operator()`. – Xeo May 30 '13 at 21:50
  • This may not be the preferred solution, but you can perhaps use `+[](const A& a){return a.a;}` as a workaround -- or if that's not enough do it in tandem with `make_function`. – Luc Danton May 31 '13 at 05:50
  • Lambdas are not special. – R. Martinho Fernandes May 31 '13 at 10:00

4 Answers4

12

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 +.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks, but will not work for capturing lambdas. Which is where lambdas start to be really useful. – eudoxos Jul 30 '15 at 12:13
  • @eudoxos Yes, I know. But in this context, I'm not sure that you actually ever need to capture anything. – Barry Jul 30 '15 at 12:28
  • @Barry, could you please provide some reference to the docs where this syntax is described? I know that it compiles and works as expected but looks like a magic trick. – Alexandra B. Jul 28 '16 at 10:41
  • @AlexandraB. See [this question and answer](http://stackoverflow.com/q/18889028/2069064) – Barry Jul 28 '16 at 11:06
5

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);
  ;
}
Tanner Sansbury
  • 51,153
  • 9
  • 112
  • 169
  • I was not able to access the [link](http://mail.python.org/pipermail/c++-sig/2003-August/005184.html) (403), but [here](http://grokbase.com/t/python/cplusplus-sig/038e1vx5sy/c-sig-callable-object-instance-as-object-method) I see Dave Abrams proposing a method, but I can't get to make it compile. What you propose does not help really, my wrapper is generated using bunch of macros and it must all stay in one scope. – eudoxos Jun 01 '13 at 11:19
  • @eudoxos: Either add the capability to Boost.Python or work within the existing limitations. One possibility to work around scope may be to assign the lambda to a global functor that is then invoked from within a non-member function. It may be helpful to update the question to more closely match the problem being solved, rather than having it remain as only a compilation problem. – Tanner Sansbury Jun 03 '13 at 15:25
  • This [answer](http://stackoverflow.com/a/18648366/1053968) goes into more details about custom functors. – Tanner Sansbury Sep 17 '15 at 16:57
4

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>
Yannick Jadoul
  • 391
  • 1
  • 11
3

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; }))
    ;
  • Will try, great. That should be proposed for inclusion in boost. – eudoxos Aug 13 '14 at 18:05
  • 1
    Trying your code, I am not able to make it work. The compiler (both clang and g++ with `--std=c++11`) say: `error: no matching function for call to 'get_signature'`, `return python::make_function(f, default_call_policies(), detail::get_signature(f, (T*)0));` and then `note: candidate template ignored: could not match 'RT (*)()' against 'std::function'` and so on. Any hint? – eudoxos Aug 22 '14 at 12:36
  • In Visual Studio it works from VS2013 and higher. Maybe your compiler does not support variadic templates? Which versions are you using? – Daan Posthuma Aug 22 '14 at 12:49
  • g++ 4.8 and clang 3.4, both pretty recent and supporting variadic templates. Boost 1.54. Strangely, the new template does not show up in the `note:` messages about candidate templates at all... – eudoxos Aug 22 '14 at 12:55
  • 1
    I had success with `.add_property("a",boost::python::detail::make_function_aux([](const A& a){return a.a;},boost::python::default_call_policies(),boost::mpl::vector())); ` from [here](https://mail.python.org/pipermail/cplusplus-sig/2009-February/014263.html) but your solution looks much more elegant. – eudoxos Aug 22 '14 at 13:07
  • Adding the function to boost seems to work. I added it to boost/python/signature.hpp in the namespace boost::python::detail. You should also include in that file. – Daan Posthuma Aug 22 '14 at 18:33