Template classes and functions#

litgen provides advanced support for template classes and functions. Refer to the examples below.

Template Functions#

Relevant portion of the pybind11 manual and of the nanobind manual

litgen can instantiate template functions for a customizable range of types.

Export template functions with an @overload decorator#

Consider the example below. If we try to generate code from it, litgen will complain that this template function is unhandled:

cpp_code = """
    template<typename T> T MaxValue(const std::vector<T>& values);
"""

import litgen
from litgen.demo import litgen_demo

options = litgen.LitgenOptions()
generated_code = litgen.generate_code(options, cpp_code)
Warning: (LitgenTemplateFunctionIgnore) Ignoring template function MaxValue. You might need to set LitgenOptions.fn_template_options
While parsing a "function_decl", corresponding to this C++ code:
Position:2:5
        template<typename T> T MaxValue(const std::vector<T>& values);
        ^
Warning: (LitgenTemplateFunctionIgnore) Ignoring template function MaxValue. You might need to set LitgenOptions.fn_template_options
While parsing a "function_decl", corresponding to this C++ code:
Position:2:5
        template<typename T> T MaxValue(const std::vector<T>& values);
        ^

If we add some information about how we want to specialize the function, then litgen will correctly output the bindings, and it will add an @overload decorator to the python functions.

options.fn_template_options.add_specialization("^MaxValue$", ["int", "float"], add_suffix_to_function_name=False)

litgen_demo.demo(options, cpp_code)
    template<typename T> T MaxValue(const std::vector<T>& values);
#  ------------------------------------------------------------------------
#      <template specializations for function MaxValue>
@overload
def max_value(values: List[int]) -> int:
    pass


@overload
def max_value(values: List[float]) -> float:
    pass
#      </template specializations for function MaxValue>
#  ------------------------------------------------------------------------


m.def("max_value",
    py::overload_cast<const std::vector<int> &>(MaxValue<int>), py::arg("values"));
m.def("max_value",
    py::overload_cast<const std::vector<float> &>(MaxValue<float>), py::arg("values"));
m.def("max_value",
    nb::overload_cast<const std::vector<int> &>(MaxValue<int>), nb::arg("values"));
m.def("max_value",
    nb::overload_cast<const std::vector<float> &>(MaxValue<float>), nb::arg("values"));

Export template functions with a suffix#

Instead of using @overload, we can give different names to the python functions:

cpp_code = """
    template<typename T> voi LogValue(const std::string& label, const T& value);
"""
options = litgen.LitgenOptions()
options.fn_template_options.add_specialization("^LogValue$", ["int", "float"], add_suffix_to_function_name=True)
litgen_demo.demo(options, cpp_code)
    template<typename T> voi LogValue(const std::string& label, const T& value);
#  ------------------------------------------------------------------------
#      <template specializations for function LogValue>
def log_value_int(label: str, value: int) -> voi:
    pass


def log_value_float(label: str, value: float) -> voi:
    pass
#      </template specializations for function LogValue>
#  ------------------------------------------------------------------------


m.def("log_value_int",
    LogValue<int>, py::arg("label"), py::arg("value"));
m.def("log_value_float",
    LogValue<float>, py::arg("label"), py::arg("value"));
m.def("log_value_int",
    LogValue<int>, nb::arg("label"), nb::arg("value"));
m.def("log_value_float",
    LogValue<float>, nb::arg("label"), nb::arg("value"));

Template classes#

Introduction#

Relevant portion of the pybind11 manual and of the nanobind manual

litgen handles template classes instantiation in a sophisticated way.

In the example below, we set the following options:

type replacements

We set an option for type name replacements, so that ImGuiConfig will be exposed as Config in python:

    options.type_replacements.add_last_replacement(r"ImGui([A-Z][a-zA-Z0-9]*)", r"\1")

class specialization

  • We tell litgen to instantiate ImVector for ImGuiConfig, float *, and int32_t.

  • We also tell it to emit a synonym (ImVector_Int32 = ImVector_int) in the python stub.

    options.class_template_options.add_specialization(
        "ImVector",  # which class do we want to specialize
        ["ImGuiConfig", "float *", "int32_t"],  # for which types
        ["Int32=uint32_t"]  # With which synonyms
        )

Notes

  • The member Foo::Configs will be exposed with the correct python type (ImVector_Config)

  • The member Foo::IntValues will not be published, since ImVector<int> is not published

  • litgen will emit a warning about the missing specialization for int

Example of template class instantiation#

cpp_code = """

    struct ImGuiConfig { /* implementation not shown here */ };

    template<typename T>
    struct ImVector
    {
        // Implementation not shown here
    private:
        T* data;
    };

    struct Foo
    {
        ImVector<ImGuiConfig> Configs; // This member will be added to the bindings
        ImVector<int> IntValues;       // This member will be excluded from the bindings, since ImVector<int> is not published!
    };
"""

options = litgen.LitgenOptions()
options.type_replacements.add_last_replacement(r"ImGui([A-Z][a-zA-Z0-9]*)", r"\1")  # Remove prefix ImGui from exposed type
options.class_template_options.add_specialization(
    "ImVector",                             # which class do we want to specialize
    ["ImGuiConfig", "float *", "int32_t"],  # for which types
    ["Int32=uint32_t"],                     # With which synonyms
)
litgen_demo.demo(options, cpp_code)

# Note: the warnings below are normal, since we did not specialize ImVector<int> (they can be filtered out, see below)
Warning: (Undefined) Excluding template type ImVector<int> because its specialization for `int` is not handled
While parsing a "type", corresponding to this C++ code:
Position:16:9
Warning: (Undefined) Excluding template type ImVector<int> because its specialization for `int` is not handled
While parsing a "type", corresponding to this C++ code:
Position:16:9
Warning: (Undefined) Excluding template type ImVector<int> because its specialization for `int` is not handled
While parsing a "type", corresponding to this C++ code:
Position:16:9
Warning: (Undefined) Excluding template type ImVector<int> because its specialization for `int` is not handled
While parsing a "type", corresponding to this C++ code:
Position:16:9
Warning: (Undefined) Excluding template type ImVector<int> because its specialization for `int` is not handled
While parsing a "type", corresponding to this C++ code:
Position:16:9
Warning: (Undefined) Excluding template type ImVector<int> because its specialization for `int` is not handled
While parsing a "type", corresponding to this C++ code:
Position:16:9
    struct ImGuiConfig { /* implementation not shown here */ };

    template<typename T>
    struct ImVector
    {
        // Implementation not shown here
    private:
        T* data;
    };

    struct Foo
    {
        ImVector<ImGuiConfig> Configs; // This member will be added to the bindings
        ImVector<int> IntValues;       // This member will be excluded from the bindings, since ImVector<int> is not published!
    };
class Config:
    # implementation not shown here
    def __init__(self) -> None:
        """Auto-generated default constructor"""
        pass

#  ------------------------------------------------------------------------
#      <template specializations for class ImVector>
class ImVector_Config:  # Python specialization for ImVector<ImGuiConfig>
    # Implementation not shown here
    def __init__(self) -> None:
        """Auto-generated default constructor"""
        pass


class ImVector_float_ptr:  # Python specialization for ImVector<float *>
    # Implementation not shown here
    def __init__(self) -> None:
        """Auto-generated default constructor"""
        pass


class ImVector_int32_t:  # Python specialization for ImVector<int32_t>
    # Implementation not shown here
    def __init__(self) -> None:
        """Auto-generated default constructor"""
        pass

ImVector_Int32 = ImVector_int

#      </template specializations for class ImVector>
#  ------------------------------------------------------------------------

class Foo:
    configs: ImVector_Config  # This member will be added to the bindings
    def __init__(self, configs: ImVector_Config = ImVector_Config()) -> None:
        """Auto-generated default constructor with named params"""
        pass


auto pyClassImGuiConfig =
    py::class_<ImGuiConfig>
        (m, "Config", "")
    .def(py::init<>()) // implicit default constructor 
    ;


auto pyClassImVector_ImGuiConfig =
    py::class_<ImVector<ImGuiConfig>>
        (m, "ImVector_Config", "")
    .def(py::init<>()) // implicit default constructor 
    ;
auto pyClassImVector_float_ptr =
    py::class_<ImVector<float *>>
        (m, "ImVector_float_ptr", "")
    .def(py::init<>()) // implicit default constructor 
    ;
auto pyClassImVector_int32_t =
    py::class_<ImVector<int32_t>>
        (m, "ImVector_int32_t", "")
    .def(py::init<>()) // implicit default constructor 
    ;


auto pyClassFoo =
    py::class_<Foo>
        (m, "Foo", "")
    .def(py::init<>([](
    ImVector<ImGuiConfig> Configs = ImVector<ImGuiConfig>())
    {
        auto r = std::make_unique<Foo>();
        r->Configs = Configs;
        return r;
    })
    , py::arg("configs") = ImVector<ImGuiConfig>()
    )
    .def_readwrite("configs", &Foo::Configs, "This member will be added to the bindings")
    ;
auto pyClassImGuiConfig =
    nb::class_<ImGuiConfig>
        (m, "Config", "")
    .def(nb::init<>()) // implicit default constructor 
    ;


auto pyClassImVector_ImGuiConfig =
    nb::class_<ImVector<ImGuiConfig>>
        (m, "ImVector_Config", "")
    .def(nb::init<>()) // implicit default constructor 
    ;
auto pyClassImVector_float_ptr =
    nb::class_<ImVector<float *>>
        (m, "ImVector_float_ptr", "")
    .def(nb::init<>()) // implicit default constructor 
    ;
auto pyClassImVector_int32_t =
    nb::class_<ImVector<int32_t>>
        (m, "ImVector_int32_t", "")
    .def(nb::init<>()) // implicit default constructor 
    ;


auto pyClassFoo =
    nb::class_<Foo>
        (m, "Foo", "")
    .def("__init__", [](Foo * self, ImVector<ImGuiConfig> Configs = ImVector<ImGuiConfig>())
    {
        new (self) Foo();  // placement new
        auto r = self;
        r->Configs = Configs;
    },
    nb::arg("configs") = ImVector<ImGuiConfig>()
    )
    .def_rw("configs", &Foo::Configs, "This member will be added to the bindings")
    ;

Suppress template class warnings#

You can ask litgen to ignore the warnings concerning the missing specialization:

# tell litgen to ignore warnings that contain "Excluding template type ImVector<int>"
options.srcmlcpp_options.ignored_warning_parts.append("Excluding template type ImVector<int>")
# the following line emits a warning that is ignored
generated_code = litgen.generate_code(options, cpp_code)