Barry
Barry

Reputation: 302758

swig pass list of tuples into C++ function

I'm trying to learn SWIG, trying to compare it with other wrappers for C++ in python. One of the functions I want to write is something like this:

obj = MyObj()
obj.add([(1,2,), (3,4.0,), (5,"six","string",)])

add needs to take a list of tuples, that are either size 2 or 3 and not all uniform type (because, you know, Python). What is the easiest way to do this in SWIG (ultimately I want this code to call obj.add_int(1, 2), obj.add_double(3, 4.0), on the actual C++ object).

Upvotes: 1

Views: 700

Answers (1)

If it were me doing this in SWIG I'd try to write the least amount of weird extra code possible. So my initial approach would be to write some Python code to handle the heterogeneous list and figure out which add_TYPE to call for each entry in the list.

As an example:

%module test

%{
#include <iostream>
#include <limits>
%}

%include <std_string.i>

%inline %{
  struct MyObj {
    void add_double(unsigned a, const double& b, const double& c=std::numeric_limits<double>::quiet_NaN()) {
      std::cout << "add_double(): " << a << ", " << b << ", " << c << "\n";
    }

    void add_int(unsigned a, int b, int c=~0) {
      std::cout << "add_int(): " << a << ", " << b << ", " << c << "\n";
    }

    void add_string(unsigned a, const std::string& b, const std::string& c="") {
      std::cout << "add_string(): " << a << ", " << b << ", " << c << "\n";
    }
  };
%}

%pythoncode %{
def obj_add(self, items):
  tries = (self.add_int, self.add_double, self.add_string)

  for args in items:
    for method in tries:
      good = False
      try:
        method(*args)
      except NotImplementedError:
        pass # Just pick a different version
      else:
        good = True
        break

    if not good: raise TypeError('No matches')

MyObj.add = obj_add
%}

For convenience of a demo I declared, defined and wrapped all of MyObj in the one %inline directive. You might have slight variations on this, e.g. overloads instead of default arguments to distinguish the two/three arg versions probably makes a lot of sense and would 'just work' with SWIG also.

The magic of add is written entirely in Python using good old fashioned ducktyping. Being plain Python all the usual approaches to this that you might want to take are open - iterating over the list and using try to handle failures until one works was my choice. (Worth noting that the order you try them in matters - if double comes before int then you'll never pick int over double).

This was sufficient to let me run:

from test import *

obj = MyObj()
obj.add([(1,2,), (3,4.0,), (5,"six","string",)])

print('Done')

Once I had compiled it with:

swig -py3 -c++ -python -Wall test.i
g++ -Wall -Wextra test_wrap.cxx -I/usr/include/python3.4/ -lpython3.4m -shared -o _test.so

And the result was as expected:

add_int(): 1, 2, -1
add_double(): 3, 4, nan
add_string(): 5, six, string
Done

You could have solved this in quite a few other ways, the most obvious other choice being to write add directly using lots of Python C API calls (PyFloat_Check, PyInt_Check etc.) but that seems like a lot of effort to write harder to maintain code when we could just rely on existing SWIG behaviour.

Upvotes: 1

Related Questions