Reputation: 33046
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
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
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