First, post. I'm sorry for any formatting errors. I'm working on porting a c++ project to a python. I cannot change the c++ code. Within this project, I'm working on porting a class template that depends on a bool as input to define the class. My various attempts at doing this has failed either where I simply don't have correct cython code or where I get error during cython's process of generating c++ code. I haven't been able to solve my problem via other stack exchange posts [1] [2] [3] or via the documentation.
For my "best" attempt, I have something that looks like the following:
In the c++ header file foo.hh
class Foo{
public:
static constexpr bool T
Foo(Foo&& x) = default;
explicit Foo(const Foo<not T>&& x)
//Other methods and constructors not relevant.
}
The project I'm working with has multiple .pxd
file. The main file exposes the c++ code while the other .pxd
files are for module specific tasks.
In main_decl.pxd
cdef cppclass Foo[T]
cdef cppclass Foo[T]:
Foo(Foo& x)
Foo(const Foo[T]&& x) # I'm unsure if this is correct.
# other methods below
in the module specific file foo.pxd
from .main_decl cimport *
cdef class Foo:
cdef Foo *thisptr
and finally the module itself Foo.pyx
# distutils: language = c++
cimport cython
cdef class Foo(object):
def __init__(self, T):
pass
def __cinit__(self):
self.thisptr = NULL
def __dealloc__(self):
del self.thisptr
In other parts of the project, the above pattern works for defining class. The error
main/foo.cpp -o build/temp.linux-x86_64-cpython-310/main/foo.o
main/foo.cpp:1591:15: error: âTâ was not declared in this scope
1591 | main::Foo<T> *thisptr;
| ^~~
main/Foo.cpp:1591:18: error: template argument 1 is invalid
1591 | main::Box<T> *thisptr;
Any advice on this would be appreciated. I suspect this has something to do with how the template name T is used but am unsure of how to approach fixing this.
I guess the challenge is to correctly wrap a C++ template class Foo in cython in such a way python wrapper can handle both Foo<true>
and Foo<false>
.
foo.hh
#pragma once
template <bool T>
class Foo {
public:
static constexpr bool value = T;
Foo() {}
Foo(Foo&& x) = default;
explicit Foo(const Foo<!T>&& x) {}
};
C++ header defines a template class Foo that depends on a boolean. where you have Foo<true>
and Foo<false>
as different types.
main_decl.pxd The C++ template specializations as separate classes:
cdef extern from "foo.hh" namespace "main":
cdef cppclass FooTrue "Foo<true>":
Foo()
Foo(FooTrue& x)
Foo(const FooFalse&& x)
cdef cppclass FooFalse "Foo<false>":
Foo()
Foo(FooFalse& x)
Foo(const FooTrue&& x)
FooTrue is cython representation of Foo<true>
, and FooFalse is for Foo<false>
. That ensures the template argument is handled correctly.
foo.pxd here you create Cython wrapper classes for the two C++ classes:
from .main_decl cimport FooTrue, FooFalse
cdef class PyFooTrue:
cdef FooTrue* thisptr
cdef class PyFooFalse:
cdef FooFalse* thisptr
PyFooTrue and PyFooFalse are classes that hold pointers to the C++ Foo<true>
and Foo<false>
objects.
Foo.pyx
PyFoo determines whether to use Foo<true>
or Foo<false>
based on the boolean T (at runtime):
from .foo cimport FooTrue, FooFalse # import the classes
cdef class PyFoo:
cdef FooTrue* thisptr_true
cdef FooFalse* thisptr_false
cdef bint is_true
def __init__(self, bint T):
if T:
self.thisptr_true = new FooTrue() # the correct class
self.is_true = True
else:
self.thisptr_false = new FooFalse() # the correct class
self.is_true = False
def __dealloc__(self):
if self.is_true:
del self.thisptr_true
else:
del self.thisptr_false
depending on the boolean T, either FooTrue or FooFalse is instantiated. The C++ object is created and destroyed correctly.
setup.py for Building the Extension:
from setuptools import setup
from Cython.Build import cythonize
from setuptools.extension import Extension
extensions = [
Extension(
name="foo.Foo", # the package structure
sources=["foo/Foo.pyx"], # source path
include_dirs=["foo", "."], # include necessary directories
language="c++",
)
]
setup(
ext_modules=cythonize(extensions)
)