Chaste / cppwg

An automatic Python wrapper generator for C++ code.
BSD 3-Clause "New" or "Revised" License
45 stars 8 forks source link

Template specializations #21

Closed kwabenantim closed 2 months ago

kwabenantim commented 6 months ago

Summary

Wrappers are created for methods in template specializations that may not have been implemented. In the example below, the Foo(unsigned u) constructor is not implemented in the specialization.

Foo.hpp

template<unsigned DIM_A, unsigned DIM_B>
class Foo : public AbstractFoo<DIM_A, DIM_B>
{
public:
  Foo(unsigned u);
  Foo(unsigned u, double d);
};

// Specialization
template<unsigned DIM_B>
class Foo<1, DIM_B> : public AbstractFoo<1,DIM_B>
{
public:
  Foo(unsigned u, double d);
  Foo(unsigned u);
};

Foo.cpp

template<unsigned DIM_A, unsigned DIM_B>
Foo<DIM_A, DIM_B>::Foo(unsigned u) : AbstractFoo<DIM_A, DIM_B>(u)
{
  // implementation
}

template<unsigned DIM_A, unsigned DIM_B>
Foo<DIM_A, DIM_B>::Foo(unsigned u, double d) : AbstractFoo<DIM_A, DIM_B>(u, d)
{
  // implementation
}

// Specialization
template<unsigned DIM_B>
Foo<1, DIM_B>::Foo(unsigned u, double d) : AbstractFoo<1, DIM_B>(u, d)
{
  // implementation
}

The wrapper below is generated for Foo<1,2>:

py::class_<Foo1_2, Foo1_2_Overloads>, AbstractFoo<1,2> >(m, "Foo1_2")
  .def(py::init<unsigned int>(), py::arg("u"))
  .def(py::init<unsigned int, double>(), py::arg("u"), py::arg("d"))

This results in a linker error:

undefined reference to `Foo<1u, 2u>::Foo(unsigned int)'
kwabenantim commented 6 months ago

One possible solution is to allow specifying exclusions for specific template arg values. That way, the unimplemented method can be omitted, resulting in the following wrapper which compiles without errors:

py::class_<Foo1_2, Foo1_2_Overloads>, AbstractFoo<1,2> >(m, "Foo1_2")
  .def(py::init<unsigned int, double>(), py::arg("u"), py::arg("d"))

The wrappers not affected by the specialization should be generated as usual e.g. for Foo<2,2>:

py::class_<Foo2_2, Foo2_2_Overloads>, AbstractFoo<2,2> >(m, "Foo2_2")
  .def(py::init<unsigned int>(), py::arg("u"))
  .def(py::init<unsigned int, double>(), py::arg("u"), py::arg("d"))
kwabenantim commented 2 months ago

This can be done by excluding the unimplemented method from wrapping for the whole class, and then adding it back in for the appropriate template args using a custom wrapper generator.

kwabenantim commented 2 months ago

The example above can be fixed in this manner:

package_info.yaml

classes:
  - name: Foo
    constructor_signature_excludes:
      - [unsigned]

FooCustomTemplate.py

class FooCustomTemplate(cppwg.templates.custom.Custom):

    def get_class_cpp_def_code(self, class_name):
        DIM_A =  # Extract DIM_A from class_name here

        if DIM_A != "1":
            return ".def(py::init<unsigned int>(), py::arg("u"))"

        return ""