First steps#

Generate bindings for functions#

Using litgen is straightforward. The simplest use case is:

  1. import litgen

  2. instantiate some options

  3. convert some code into C++ binding code and python declarations (stubs)

  4. use the generated code

Below we import litgen, define the C++ code we want to bind, and run litgen.generate_code

# Import litgen
import litgen


# Instantiate some options
options = litgen.LitgenOptions()
# Code for which we will emit bindings
cpp_code = """
    // Mathematic operations

    // Adds two integers
    int Add(int x, int y = 2);

    int Sub(int x, int y = 2); // Substract two integers
    """
# Run the generator
generated_code = litgen.generate_code(options, cpp_code)

The generated code contains several elements:

  • stub_code contains the python declarations, including all comments. They enable to have a flawless code navigation and completion inside IDEs.

  • pydef_code contains the C++ binding code. This code is specific to pybind11

  • glue_code: optional additional C++ code that is emitted in speficic advanced cases (more details later in this manual). Most of the time, it will be empty

Let’s see the python declarations corresponding to the C++ code: they should be copied into a python stub file (*.pyi) that describes the bindings offered by your library.

Note: below, we import litgen_demo to be able to display code in this page. You will not need it: you can simply call print(generated_code.stub_code)

from litgen.demo import litgen_demo

litgen_demo.demo(options, cpp_code, show_pydef=True)

# Below, you will see:
# - the original C++ code to the left
# - stub_code (i.e. the python stubs) to the right
# - pydef_code (i.e. the C++ binding code) at the bottom
# In this case, the glue code is empty 
    // Mathematic operations

    // Adds two integers
    int Add(int x, int y = 2);

    int Sub(int x, int y = 2); // Substract two integers
    
# Mathematic operations

def add(x: int, y: int = 2) -> int:
    """ Adds two integers"""
    pass

def sub(x: int, y: int = 2) -> int:
    """ Substract two integers"""
    pass


m.def("add",
    Add, 
    py::arg("x"), py::arg("y") = 2, 
    "Adds two integers");

m.def("sub",
    Sub, 
    py::arg("x"), py::arg("y") = 2, 
    "Substract two integers");
m.def("add",
    Add, 
    nb::arg("x"), nb::arg("y") = 2, 
    "Adds two integers");

m.def("sub",
    Sub, 
    nb::arg("x"), nb::arg("y") = 2, 
    "Substract two integers");

Binding a simple class or struct#

For a simple class, public members and methods will be exposed. For a simple struct, a constructor with named parameters will be exposed in Python.

options = litgen.LitgenOptions()
cpp_code = """
class FooClass {
    private:
        int mPriv = 0;
    public:
        FooClass(int v);
        int mPublic = 1;
        void ShowInfo();
};

struct FooStruc {
    int a = 1;
    void ShowInfo();
};
"""

litgen_demo.demo(options, cpp_code, show_pydef=True)
class FooClass {
    private:
        int mPriv = 0;
    public:
        FooClass(int v);
        int mPublic = 1;
        void ShowInfo();
};

struct FooStruc {
    int a = 1;
    void ShowInfo();
};
class FooClass:
    def __init__(self, v: int) -> None:
        pass
    m_public: int = 1
    def show_info(self) -> None:
        pass

class FooStruc:
    a: int = 1
    def show_info(self) -> None:
        pass
    def __init__(self, a: int = 1) -> None:
        """Auto-generated default constructor with named params"""
        pass


auto pyClassFooClass =
    py::class_<FooClass>
        (m, "FooClass", "")
    .def(py::init<int>(),
        py::arg("v"))
    .def_readwrite("m_public", &FooClass::mPublic, "")
    .def("show_info",
        &FooClass::ShowInfo)
    ;


auto pyClassFooStruc =
    py::class_<FooStruc>
        (m, "FooStruc", "")
    .def(py::init<>([](
    int a = 1)
    {
        auto r = std::make_unique<FooStruc>();
        r->a = a;
        return r;
    })
    , py::arg("a") = 1
    )
    .def_readwrite("a", &FooStruc::a, "")
    .def("show_info",
        &FooStruc::ShowInfo)
    ;
auto pyClassFooClass =
    nb::class_<FooClass>
        (m, "FooClass", "")
    .def(nb::init<int>(),
        nb::arg("v"))
    .def_rw("m_public", &FooClass::mPublic, "")
    .def("show_info",
        &FooClass::ShowInfo)
    ;


auto pyClassFooStruc =
    nb::class_<FooStruc>
        (m, "FooStruc", "")
    .def("__init__", [](FooStruc * self, int a = 1)
    {
        new (self) FooStruc();  // placement new
        auto r = self;
        r->a = a;
    },
    nb::arg("a") = 1
    )
    .def_rw("a", &FooStruc::a, "")
    .def("show_info",
        &FooStruc::ShowInfo)
    ;

Generator options#

There are numerous generations options that can be set, and they are explained in details later in this manual: See full list

About glue code#

In some cases, some additional “glue code” will be emitted. This is the case for example when we want to bind a function that wants to modify a parameter whose type is immutable in python.

In this case, we will need to set one option (options.fn_params_replace_modifiable_immutable_by_boxed__regex )

options = litgen.LitgenOptions()
# This a regex which specifies that we want to adapt all functions with boxed types when needed
options.fn_params_replace_modifiable_immutable_by_boxed__regex = r".*"
cpp_code = """
    // changes the value of the bool parameter (passed by modifiable reference)
    // (This function will use a BoxedBool in the python code, so that its value can be modified)
    void SwitchBoolValue(bool &v);
    """
# Run the generator
litgen_demo.demo(options, cpp_code, show_pydef=True)
    // changes the value of the bool parameter (passed by modifiable reference)
    // (This function will use a BoxedBool in the python code, so that its value can be modified)
    void SwitchBoolValue(bool &v);
    
####################    <generated_from:BoxedTypes>    ####################
class BoxedBool:
    value: bool
    def __init__(self, v: bool = False) -> None:
        pass
    def __repr__(self) -> str:
        pass
####################    </generated_from:BoxedTypes>    ####################



def switch_bool_value(v: BoxedBool) -> None:
    """ changes the value of the bool parameter (passed by modifiable reference)
     (This function will use a BoxedBool in the python code, so that its value can be modified)
    """
    pass


////////////////////    <generated_from:BoxedTypes>    ////////////////////
auto pyClassBoxedBool =
    py::class_<BoxedBool>
        (m, "BoxedBool", "")
    .def_readwrite("value", &BoxedBool::value, "")
    .def(py::init<bool>(),
        py::arg("v") = false)
    .def("__repr__",
        &BoxedBool::__repr__)
    ;
////////////////////    </generated_from:BoxedTypes>    ////////////////////


m.def("switch_bool_value",
    [](BoxedBool & v)
    {
        auto SwitchBoolValue_adapt_modifiable_immutable = [](BoxedBool & v)
        {
            bool & v_boxed_value = v.value;

            SwitchBoolValue(v_boxed_value);
        };

        SwitchBoolValue_adapt_modifiable_immutable(v);
    }, 
    py::arg("v"), 
    " changes the value of the bool parameter (passed by modifiable reference)\n (This function will use a BoxedBool in the python code, so that its value can be modified)");
////////////////////    <generated_from:BoxedTypes>    ////////////////////
auto pyClassBoxedBool =
    nb::class_<BoxedBool>
        (m, "BoxedBool", "")
    .def_rw("value", &BoxedBool::value, "")
    .def(nb::init<bool>(),
        nb::arg("v") = false)
    .def("__repr__",
        &BoxedBool::__repr__)
    ;
////////////////////    </generated_from:BoxedTypes>    ////////////////////


m.def("switch_bool_value",
    [](BoxedBool & v)
    {
        auto SwitchBoolValue_adapt_modifiable_immutable = [](BoxedBool & v)
        {
            bool & v_boxed_value = v.value;

            SwitchBoolValue(v_boxed_value);
        };

        SwitchBoolValue_adapt_modifiable_immutable(v);
    }, 
    nb::arg("v"), 
    " changes the value of the bool parameter (passed by modifiable reference)\n (This function will use a BoxedBool in the python code, so that its value can be modified)");

struct BoxedBool
{
    bool value;
    BoxedBool(bool v = false) : value(v) {}
    std::string __repr__() const { return std::string("BoxedBool(") + std::to_string(value) + ")"; }
};