Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost python overload operator ()

I would like to bind the operator() using Boost::Python but I don't really see how to do this. Consider the example:

C++:

class Queuer
{ 

public:
void Queuer::operator()(const qfc::Queue & iq, const qfc::Message & im) const;
void Queuer::operator()(const qfc::Agent & ia, const qfc::Message & im) const;
// some other overloaded operator() methods

};

So in a Python script, after importing the module I'm using (called qfc), I would like to do:

Python:

>>> queuer = qfc.Queuer()
// instantiating a Message an Agent and a Queue object
>>> queuer(queue,message)
>>> queuer(agent,message)
>>> ...

Would you have any idea on how to do it? maybe with boost::python call<>?

Thank you, Kevin

like image 488
Eon Kevin Avatar asked Aug 29 '13 09:08

Eon Kevin


People also ask

What is operator overloading in Python?

This feature in Python, that allows same operator to have different meaning according to the context is called operator overloading.

Why ‘+’ operator is overloaded in C++?

It is achievable because ‘+’ operator is overloaded by int class and str class. You might have noticed that the same built-in operator or function shows different behavior for objects of different classes, this is called Operator Overloading .

How to overload a static method in Python?

To create overloaded staticmethods in python, you overload first, then you make it static. If you want to expose a static function that returns a pointer to an already exposed C++ type, you have to specify a return policy. This is because python needs to know what to do with that pointer and how to track it after it is returned.

How do operators work in Python?

Python operators work for built-in classes. But the same operator behaves differently with different types. For example, the + operator will perform arithmetic addition on two numbers, merge two lists, or concatenate two strings. This feature in Python that allows the same operator to have different meaning according to ...


1 Answers

When exposing the Queuer class, define a __call__ method for each Queuer::operator() member function. Boost.Python will handle the appropriate dispatching based on types. The only complexity is introduced with pointer-to-member-function syntax, as the caller is required to disambiguate &Queuer::operator().

Additionally, when attempting to pass derived classes in Python to a C++ function with a parameter of the Base class, then some additional information needs to be exposed to Boost.Python:

  • The base C++ class needs to be exposed with class_. For example, class_<BaseType>("Base").
  • The derived class needs to explicitly list its base classes when being exposed with bases_. For example, class_<DerivedType, bases<BaseType> >("Derived"). With this information, Boost.Python can do proper casting while dispatching.

Here is a complete example:

#include <iostream>

#include <boost/python.hpp>

// Mockup classes.
struct AgentBase   {};
struct MessageBase {};
struct QueueBase   {};
struct SpamBase    {};
struct Agent:   AgentBase   {};
struct Message: MessageBase {};
struct Queue:   QueueBase   {};
struct Spam:    SpamBase    {};

// Class with overloaded operator().
class Queuer
{ 
public:

  void operator()(const AgentBase&, const MessageBase&) const
  {
    std::cout << "Queuer::operator() with Agent." << std::endl;
  }

  void operator()(const QueueBase&, const MessageBase&) const
  {
    std::cout << "Queuer::operator() with Queue." << std::endl;
  }

  void operator()(const SpamBase&, const MessageBase&) const
  {
    std::cout << "Queuer::operator() with Spam." << std::endl;
  }
};

/// Depending on the overlaod signatures, helper types may make the
/// code slightly more readable by reducing pointer-to-member-function syntax.
template <typename A1>
struct queuer_overload
{
  typedef void (Queuer::*type)(const A1&, const MessageBase&) const;
  static type get(type fn) { return fn; }
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  // Expose only the base class types.  Do not allow the classes to be
  // directly initialized in Python.
  python::class_<AgentBase  >("AgentBase",   python::no_init);
  python::class_<MessageBase>("MessageBase", python::no_init);
  python::class_<QueueBase  >("QueueBase",   python::no_init);
  python::class_<SpamBase   >("SpamBase",    python::no_init);

  // Expose the user types.  These classes inerit from their respective
  // base classes.
  python::class_<Agent,   python::bases<AgentBase>   >("Agent");
  python::class_<Message, python::bases<MessageBase> >("Message");
  python::class_<Queue,   python::bases<QueueBase>   >("Queue");
  python::class_<Spam,    python::bases<SpamBase>    >("Spam");

  // Disambiguate via a varaible.
  queuer_overload<AgentBase>::type queuer_op_agent = &Queuer::operator();

  python::class_<Queuer>("Queuer")
    // Disambiguate via a variable.
    .def("__call__", queuer_op_agent)
    // Disambiguate via a helper type.
    .def("__call__", queuer_overload<QueueBase>::get(&Queuer::operator()))
    // Disambiguate via explicit cast.
    .def("__call__",
         static_cast<void (Queuer::*)(const SpamBase&, 
                                      const MessageBase&) const>(
             &Queuer::operator()))
    ;
}

And its usage:

>>> import example
>>> queuer = example.Queuer()
>>> queuer(example.Agent(), example.Message())
Queuer::operator() with Agent.
>>> queuer(example.Queue(), example.Message())
Queuer::operator() with Queue.
>>> queuer(example.Spam(), example.Message())
Queuer::operator() with Spam.
like image 97
Tanner Sansbury Avatar answered Oct 11 '22 21:10

Tanner Sansbury