4

With the help of https://stackoverflow.com/a/22965961/353337, I was able to create a simple example of how to pass one function pointer into a function via Python. Specifically, with

double f(double x) {
  return x*x;
}

double myfun(double (*f)(double x)) {
  fprintf(stdout, "%g\n", f(2.0));
  return -1.0;
}
%module test

%{
#include "test.hpp"
%}

%pythoncallback;
double f(double);
%nopythoncallback;

%ignore f;
%include "test.hpp"

I can call

import test
test.f(13)
test.myfun(test.f)

and get the expected results.

Now, I would like to change the signature of myfun to allow for an array of function pointers (all with the same signature), e.g.,

double myfun(std::vector<double (*)(double)>)

How do I have adapt the .i file?

Ideally, the Python call would be via a list

test.myfun([test.f, test.g])
Community
  • 1
  • 1
Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • @πάντα ῥεῖ The basic working example is the same, but the question is different. Please unmark as dup. – Nico Schlömer Aug 19 '16 at 21:56
  • Please improve your question 1st. – πάντα ῥεῖ Aug 19 '16 at 21:58
  • @NicoSchlömer You can use the basic code provided for this question, http://stackoverflow.com/questions/34445045/passing-python-functions-to-swig-wrapped-c-code What you could do to support multiple signatures is to keep an array of function pointers and whenever you assign a python function you need to specify the signature to be one of the supported signatures. – Jens Munk Aug 20 '16 at 04:58
  • Thanks @JensMunk for the comment. I don't understand the multiple signatures hint -- In fact, all the functions that I'd like to pass in should have the same signature. I clarified the question in this regard. – Nico Schlömer Aug 20 '16 at 08:09
  • 1
    Well I was expecting the answer to this to be "just use `%template(PfnVec) std::vector;`, but that doesn't seem to work properly. Looking at a workaround. – Flexo Aug 20 '16 at 14:41

1 Answers1

1

I made the following test case to illustrate what you're trying to do. It has a real implementation of myfun(const std::vector<double(*)(double)>&) to make life a little more interesting:

#include <vector>

double g(double x) {
  return -x;
}

double f(double x) {
  return x*x;
}

typedef double(*pfn_t)(double);

std::vector<double> myfun(const std::vector<pfn_t>& funs, const double d) {
  std::vector<double> ret;
  ret.reserve(funs.size());
  for(auto && fn : funs)
    ret.emplace_back(fn(d));
  return ret;
}

I expected that all we'd need to do to make this work is use:

%include <std_vector.i>
%template(FunVec) std::vector<double(*)(double)>;
%template(DoubleVec) std::vector<double>;
%include "test.h"

However SWIG 3.0 (from Debian stable) doesn't handle this FunVec correctly and the resulting module doesn't compile. So I added a typemap as a workaround:

%module test

%{
#include "test.h"
%}

%pythoncallback;
double f(double);
double g(double);
%nopythoncallback;

%ignore f;
%ignore g;

%typemap(in) const std::vector<pfn_t>& (std::vector<pfn_t> tmp) {
    // Adapted from: https://docs.python.org/2/c-api/iter.html
    PyObject *iterator = PyObject_GetIter($input);
    PyObject *item;

    if (iterator == NULL) {
      assert(iterator);
      SWIG_fail; // Do this properly
    }

    while ((item = PyIter_Next(iterator))) {
        pfn_t f;
        const int res = SWIG_ConvertFunctionPtr(item, (void**)(&f), $descriptor(double(*)(double)));
        if (!SWIG_IsOK(res)) {
          assert(false);
          SWIG_exception_fail(SWIG_ArgError(res), "in method '" "foobar" "', argument " "1"" of type '" "pfn_t""'");
        }
        Py_DECREF(item);
        tmp.push_back(f);
    }

    Py_DECREF(iterator);
    $1 = &tmp;
}

%include <std_vector.i>
// Doesn't work:
//%template(FunVec) std::vector<double(*)(double)>;
%template(DoubleVec) std::vector<double>;
%include "test.h"

Basically all this does is add one 'in' typemap for the vector of function pointer types. That typemap just iterates over the input given from Python and builds a temporary std::vector from a Python iterable.

This is sufficient that the following Python works as expected:

import test

print test.g
print test.f
print test.g(666)
print test.f(666)

print test.myfun([test.g,test.f],123)
Flexo
  • 87,323
  • 22
  • 191
  • 272