aaragon
aaragon

Reputation: 2400

swig: extending a class template to provide __str__

Say you have a template class Foo, and you want to wrap it with Swig transparently so that you can print the class:

>>> from example import *
>>> f = Foo2()
>>> print(f)
In Foo class!

I have followed this post and this one. So my header file is:

#include <iostream>

template <int d> class Foo {

public:

  friend std::ostream &operator<<(std::ostream &os, const Foo &m) {
    os << "Inside Foo class!" << std::endl;
    return os;
  }
};

And my interface file:

%{
#include <sstream>
#include <iostream>
#include "foo.hpp"
%}

%include "std_iostream.i"

// Try grabbing it unmodified
%include "foo.hpp"


/* Instantiate a few different versions of the template */
%template(Foo2) Foo<2>;
%template(Foo3) Foo<3>;


%extend Foo<2> {

  const char *__str__() {

    std::ostringstream oss(std::ostringstream::out);
    oss << *self;
    return oss.str().c_str();
  }
};

So this works just fine, I can print the object as before, but I would like to generalize it for any value of the template parameter, as it doesn't make sense to copy that code for every template parameter. I tried the following in the interface file but it didn't work:

template <int d> class Foo {

public:

  %extend {
    const char *__str__() {

      std::ostringstream oss(std::ostringstream::out);
      oss << *self;
      return oss.str().c_str();
    }
  }
};

Upvotes: 4

Views: 1096

Answers (2)

Oktalist
Oktalist

Reputation: 14714

You should be able to %extend the primary template, from outside its definition, by omitting the template parameter list:

%extend Foo {
  const char *__str__() {
    std::ostringstream oss(std::ostringstream::out);
    oss << *self;
    return oss.str().c_str();
  }
};

%template(Foo2) Foo<2>;
%template(Foo3) Foo<3>;

Or you could use a SWIG macro to wrap and extend each specialization in one go:

%define WRAP_FOO(N)

  %template( Foo ## N ) Foo<N>;

  %extend Foo<N> {
    const char *__str__() {
      std::ostringstream oss(std::ostringstream::out);
      oss << *self;
      return oss.str().c_str();
    }
  };

%enddef

/* Instantiate a few different versions of the template */
WRAP_FOO(2)
WRAP_FOO(3)

Note that in either case, you are causing undefined behaviour by returning the result of .c_str() of a std::string which is destroyed before the function returns.

Upvotes: 3

lefticus
lefticus

Reputation: 3416

The %extend syntax you are using in your final example should be correct, this is a technique we are using in OpenStudio

I believe the problem is that you are defining the template 2 times, once in your %import directive and once in your .i file. The first definition is the one that SWIG is using.

While not ideal, I believe that you'll need to drop the %include "foo.hpp" directive and define specifically the interface you want. Your new .i file becomes now something like:

%{
#include <sstream>
#include <iostream>
#include "foo.hpp"
%}

%include "std_iostream.i"

// Don't let SWIG directly parse foo.hpp
// %include "foo.hpp"



template <int d> class Foo {

public:
  // include here prototypes for all functions
  // you want exposed. You don't need the implementation like in 
  // a normal C++ template declaration

  // include here any extensions you want to add
  %extend {
    const char *__str__() {

      std::ostringstream oss(std::ostringstream::out);
      oss << *self;
      return oss.str().c_str();
    }
  }
};

/* Instantiate a few different versions of the template */
%template(Foo2) Foo<2>;
%template(Foo3) Foo<3>;

Alternatively, you could place the SWIG code in your hpp file directly and save having to maintain two APIs:

New .i file:

%{
#include <sstream>
#include <iostream>
#include "foo.hpp"
%}

%include "std_iostream.i"

// let swig directly parse foo.hpp
%include "foo.hpp"


/* Instantiate a few different versions of the template */
%template(Foo2) Foo<2>;
%template(Foo3) Foo<3>;

New .hpp file:

#include <iostream>

template <int d> class Foo {

public:
#ifdef SWIG
  %extend {
    const char *__str__() {

      std::ostringstream oss(std::ostringstream::out);
      oss << *self;
      return oss.str().c_str();
    }
  }
#endif

  friend std::ostream &operator<<(std::ostream &os, const Foo &m) {
    os << "Inside Foo class!" << std::endl;
    return os;
  }
};

Upvotes: 1

Related Questions