10

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
  • Did you try add_property<int(const A&)>("a",[](const A& a){return a.a;})? It looks like boost::python cannot figure out the function signature. Commented May 30, 2013 at 21:29
  • I'm guessing that get_signature only checks for normal and member function pointers, not function objects' operator(). Commented May 30, 2013 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. Commented May 31, 2013 at 5:50
  • Lambdas are not special. Commented May 31, 2013 at 10:00

4 Answers 4

13

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

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, but will not work for capturing lambdas. Which is where lambdas start to be really useful.
@eudoxos Yes, I know. But in this context, I'm not sure that you actually ever need to capture anything.
@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.
@AlexandraB. See this question and answer
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);
  ;
}

3 Comments

I was not able to access the link (403), but here 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: 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.
This answer goes into more details about custom functors.
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>

Comments

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; }))
    ;

6 Comments

Will try, great. That should be proposed for inclusion in boost.
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<int (const A &)>' and so on. Any hint?
In Visual Studio it works from VS2013 and higher. Maybe your compiler does not support variadic templates? Which versions are you using?
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...
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<int,A>())); from here but your solution looks much more elegant.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.