Stefano Sanfilippo
Stefano Sanfilippo

Reputation: 33046

Handling std::function with SWIG

Apparently, SWIG does not understand std::function and breaks Python bindings. For instance, this works in C++:

// Somewhere in the API
typedef std::function<void(const UnitError & error)> UnitErrorHandler;

// Somewhere else in the API
void Unit::setErrorHandler(const UnitErrorHandler & handler) {}

// In the application code
unit->setErrorHandler([](const UnitError & error){
    std::cerr << error << std::endl;
    std::exit(1);
});

But this will break code (apart from having a different behaviour for the sake of simplicity, but that's not the point):

unit.setErrorHandler(lambda error: len(error))

The situation is the same with def (ordinary) unbound functions. So, does anyone know a workaround for this?

Upvotes: 5

Views: 2243

Answers (2)

DarioP
DarioP

Reputation: 5465

In two lines: create an interface function that takes a command as a string and sets up a functor that is then assigned to the std::function. The operator() takes care of evaluating the string in the target language.


Let's assume that in the header file mymodule.hh you have a class MyModule with a method void MyModule::Func(const std::function<double(Bunch&)> & func) where Bunch is another object.

This can be solved defining an interface function that takes a const char * representing an expression to eval and wraps it into a functor that will be then assigned to the std::function. The good news is that this can entirely done in the swig interface file, without touching the C++ code, here is how (I used tcl, you just have to adapt the operator()):

%module mymodule
%{
  #include "bunch.hh"
  #include "mymodule.hh"

  extern Tcl_Interp* tcl_interp;

  struct Tcl_bunch_callback {
    std::string callback;
    double operator()(Bunch & bunch)
    {
      Tcl_Obj * bunch_obj = SWIG_Tcl_NewInstanceObj(tcl_interp, &bunch, SWIGTYPE_p_Bunch, /*own pointer?*/0);
      Tcl_SetVar2Ex(tcl_interp, "bunch", (const char*)nullptr, bunch_obj, 0);
      double resultValue;
      const int resultCode = Tcl_ExprDouble(tcl_interp, callback.c_str(), &resultValue);
      if (resultCode != TCL_OK) {
        std::cerr << "WARNING evaluation of tcl expression failed: " 
                  << Tcl_GetStringResult(tcl_interp) << std::endl;
        resultValue = max_double;
      }
      Tcl_DeleteCommand(tcl_interp, Tcl_GetString(bunch_obj)); //remove the used command to avoid leaks
      return resultValue;
    }
  };
%}

%include "bunch.hh"
%include "mymodule.hh"
%extend MyModule
{
  void Func(const char * cmd) {
    $self->Func(std::function<double(Bunch&)>(Tcl_bunch_callback(cmd)));
  }
}

In my case the operator() is pretty bounded to Tcl, but I'm sure that similar procedures can be written also for the others target languages. Follows some details of it.

I give the user the capability to access the methods of the current Bunch being processed in C++ directly from Tcl. The function: SWIG_NewInstanceObj allows to convert the Bunch pointer in its representation in the target language and creates an instance of it in the interpreter (this function is not documented, but digging a bit in whatever swig-generated wrap file it is not too hard to understand its mechanism). With the following command I set that object to a variable named bunch so that it becomes available to the user simply with $bunch and all the methods exported with swig are then accessible.

I think it's amazing how such powerful things are made available with such few lines of code thanks to swig!

Upvotes: 1

Mark Tolonen
Mark Tolonen

Reputation: 177745

std::function is relatively new (C++11) so SWIG doesn't have any out-of-the-box solution for it. While not as flexible, you can use function pointers, with a caveat. Quoting 5.4.9 Pointers to functions and callbacks in the docs:

...SWIG provides full support for function pointers provided that the callback functions are defined in C and not in the target language.

So passing a lambda will not work. Read the documentation link for some alternatives.

Upvotes: 1

Related Questions