Pyrex / Cython で C++ の参照を扱う方法
またまた頭が悪いせいで予想以上に時間をかけてしまった。
例えば次のようなクラスを Pyrex (Cython) でラップすることを考える。
template<typename T_> struct position: public boost::array<T_, 3> { position() { (*this)[0] = 0; (*this)[1] = 0; (*this)[2] = 0; } position(T_ x, T_ y, T_ z) { (*this)[0] = x; (*this)[1] = y; (*this)[2] = z; } T_& x() { return (*this)[0]; } const T_& x() const { return (*this)[0]; } T_& y() { return (*this)[1]; } const T_& y() const { return (*this)[1]; } T_& z() { return (*this)[2]; } const T_& z() const { return (*this)[2]; } };
このとき、x, y, z を Python のプロパティとして expose するにはどうすべか、という話。Pyrex (Cython) は基本的に C の文法しか理解しないので、次の宣言は通らない。
cdef extern from "position.hpp": ctypedef struct __impl_position "position<double>": double& x() double& y() double& z() __impl_position *__impl_position_new "new position<double>" (double x, double y, double z) void __impl_position_del "delete" (__impl_position *)
エラー内容 (Cython の場合):
cythoning test.pyx to test.cpp Error converting Pyrex file to C: ------------------------------------------------------------ ... cdef extern from "position.hpp": ctypedef struct __impl_position "position<double>": double& x() ^ ------------------------------------------------------------ test.pyx:3:14: Empty declarator
また、当然次のようなコードでは lvalue assignment だと文句を言う。
cdef extern from "position.hpp": ctypedef struct __impl_position "position<double>": double x() double y() double z() __impl_position *__impl_position_new "new position<double>" (double x, double y, double z) void __impl_position_del "delete" (__impl_position *) cdef class Position: cdef __impl_position *pimpl def __cinit__(self, double x = 0, double y = 0, double z = 0): self.pimpl = __impl_position_new(x, y, z) def __dealloc__(self): __impl_position_del(self.pimpl) property x: def __get__(self): return self.pimpl.x() def __set__(self, val): self.pimpl.x() = val property y: def __get__(self): return self.pimpl.y() def __set__(self, val): self.pimpl.y() = val property z: def __get__(self): return self.pimpl.z() def __set__(self, val): self.pimpl.z() = val
エラー内容:
Error converting Pyrex file to C: ------------------------------------------------------------ ... def __dealloc__(self): __impl_position_del(self.pimpl) property x: def __get__(self): return self.pimpl.x() def __set__(self, val): self.pimpl.x() = val ^ ------------------------------------------------------------ test.pyx:22:44: Cannot assign to or delete this
仕方なく、次のようなヘルパーを作って解決。ちょっと黒魔術的な気がするけど。
#ifndef HELPER_HPP #define HELPER_HPP namespace helper { template<typename T_> inline void ref_set(T_& ref, const T_& val) { ref = val; } } // namespace helper #endif /* HELPER_HPP */
で、
cdef extern from "helper.hpp": void __helper_set_double "helper::ref_set<double>" (double, double) cdef extern from "position.hpp": ctypedef struct __impl_position "position<double>": double x() double y() double z() __impl_position *__impl_position_new "new position<double>" (double x, double y, double z) void __impl_position_del "delete" (__impl_position *) cdef class Position: cdef __impl_position *pimpl def __cinit__(self, double x = 0, double y = 0, double z = 0): self.pimpl = __impl_position_new(x, y, z) def __dealloc__(self): __impl_position_del(self.pimpl) property x: def __get__(self): return self.pimpl.x() def __set__(self, val): __helper_set_double(self.pimpl.x(), val) property y: def __get__(self): return self.pimpl.y() def __set__(self, val): __helper_set_double(self.pimpl.y(), val) property z: def __get__(self): return self.pimpl.z() def __set__(self, val): __helper_set_double(self.pimpl.z(), val)
ちゃんと動くかためしてみる。
Python 2.5.1 (r251:54863, Mar 7 2008, 04:10:12) [GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from test import Position >>> p = Position() >>> p.x = 1.0 >>> p.y = 2.0 >>> p.z = 3.0 >>> print p.x, p.y, p.z 1.0 2.0 3.0
今回のテストで使ったファイルは以下よりダウンロードできます。
pyrex-cxx-ref.tar.bz2