swig:扩展类模板以提供__str__

4 投票
2 回答
1062 浏览
提问于 2025-04-18 16:50

假设你有一个模板类 Foo,你想用 Swig 来包装它,这样你就可以直接打印这个类的内容。

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

我参考了 这篇帖子那篇帖子。所以我的头文件是:

#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;
  }
};

而我的接口文件是:

%{
#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();
  }
};

这样做是没问题的,我可以像以前一样打印对象,但我想让这个功能适用于任何模板参数的值,因为为每个模板参数复制代码是没有意义的。我在接口文件中尝试了以下内容,但没有成功:

template <int d> class Foo {

public:

  %extend {
    const char *__str__() {

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

2 个回答

1

你在最后一个例子中使用的 %extend 语法应该是正确的,这是一种我们在 OpenStudio 中使用的技巧。

我觉得问题在于你定义模板的地方有点重复,你在 %import 指令中定义了一次,在 .i 文件中又定义了一次。SWIG 实际上使用的是第一次定义的内容。

虽然这样不是最理想的情况,但我认为你需要去掉 %include "foo.hpp" 指令,明确地定义你想要的接口。你新的 .i 文件现在应该像这样:

%{
#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>;

另外,你也可以直接把 SWIG 代码放在你的 hpp 文件里,这样就不需要维护两个 API 了:

新的 .i 文件:

%{
#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>;

新的 .hpp 文件:

#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;
  }
};
3

你可以通过省略模板参数列表,从模板的定义外部来%extend主模板:

%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>;

或者你可以使用一个SWIG宏,一次性包裹并扩展每个特化:

%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)

请注意,在这两种情况下,你都会导致未定义的行为,因为你返回了一个在函数返回之前就被销毁的std::string.c_str()结果。

撰写回答