3

I am creating Python interfaces to some C++ code that I cannot change, using SWIG. One of the C++ classes has a constructor that creates a partially initialized object that cannot be used as yet, an initialization function has to be called on it first. I want to remedy this in Python by providing an alternative constructor that takes care of doing both things (acquisition and initialization) at the same time. Let's say in C++ I have

class X {
 public:
  X() {...}
  void init(T a) {...}
  ...
};

In C++ I have to instantiate X as

X x;
x.init(a);

In Python I would like to do

x = X(a)

My solution is a hack that depends on the target language and the specific way SWIG generates the wrapper code: in my .i file I have

%inline %{
X* new_X(T a) {
  X* ret = new X();
  ret->init(a);
  return ret;
}

%nodefaultctor X;
class X {
 public:
  ...
  %extend {
    %pythoncode {
      def __init__(self, *args):
          this = _modulename.new_X(*args)
          try:
            self.this.append(this)
          except:
            self.this = this
    }
  }
};

This works fine, but it is not very satisfactory:

  • it depends on how SWIG wraps constructors internally
  • it is totally target language dependent

This seems to be a relatively common use case, so does anyone know if there is a standard way?

Mangu Singh Rajpurohit
  • 10,806
  • 4
  • 68
  • 97
doetoe
  • 765
  • 7
  • 16
  • I don't see many ways around it. To have language independent interface you would need to extend c++ class, where you cannot made constructor that will call other constructor (unless C++11). – V-master Nov 06 '15 at 11:27
  • I was thinking of a SWIG command. If SWIG would recognize my inline new_X above as a constructor (or better, if I could %extend the class with a constructor in a similar way), the whole __init__ part wouldn't be necessary, and could be created in the appropriate way for the specific target language. – doetoe Nov 06 '15 at 11:35

2 Answers2

6

The current answer by V-master does not work as is. But it can be made to work:

%ignore X::X();

// declaration of class X, e.g. %include X.h

%extend X {
    X(T a) {
        X* newX = new X();
        newX->init(a);
        return newX;
    }
};

Admittedly, this looks a little dubious, but it works, and is essentially an example from the SWIG documentation here.

It is important to note that:

%extend works with both C and C++ code. It does not modify the underlying object in any way---the extensions only show up in the Python interface.

So what this really does is to create a method (not even a class method, actually) that creates a new instance of X, calls init(a) on it and returns it. Because the syntax somewhat resembles a constructor, SWIG will wrap it as such.

m7thon
  • 3,033
  • 1
  • 11
  • 17
  • Great, this was exactly what I was looking for! – doetoe Nov 10 '15 at 18:03
  • Thank you! This worked great for C-Python Swig API. From a memory perspective, if we customize the constructor, do we need to override the destructor to delete X() that is created here or we leave it to the default swig generated delete_* function/destructor? – Maverickgugu Jul 24 '23 at 23:39
2

you can try something like this:

%ignore X::X();
%extend X {
  X(T a) {
    init(a);
  }
};

this will hide default no-parameter constructor and add new that takes T

Downside is that if ignored one were doing something, then you would need to copy that into this new one as you cannot call other constructor from within same class constructor (unless you are using C++11)

V-master
  • 1,957
  • 15
  • 18
  • Are you sure this should work? It is exactly the kind of thing I would be looking for, but I don't think it is possible (for sure I don't get it to work). Note that on the C++ level no new constructor would be defined, that is not even possible outside the class. – doetoe Nov 06 '15 at 18:08