Classes and structs#

Exclude members and classes#

Sometimes, you may want to exclude classes and/or members from the bindings. Look at the example below for instructions:

import litgen
from litgen.demo import litgen_demo

cpp_code = """
    class FooDetails // A class that we want to exclude from the bindings
    {
        // ...
    };

    struct Foo 
    {
        int X = 0, Y = 1;

        FooDetails mDetails = {}; // A member that we would want to exclude from the bindings
    };
"""


options = litgen.LitgenOptions()
options.class_exclude_by_name__regex = r"Details$"
options.member_exclude_by_name__regex = r"Details$"
litgen_demo.demo(options, cpp_code)
    class FooDetails // A class that we want to exclude from the bindings
    {
        // ...
    };

    struct Foo 
    {
        int X = 0, Y = 1;

        FooDetails mDetails = {}; // A member that we would want to exclude from the bindings
    };
class Foo:
    x: int = 0
    y: int = 1

    def __init__(self, x: int = 0, y: int = 1) -> None:
        """Auto-generated default constructor with named params"""
        pass


auto pyClassFoo =
    py::class_<Foo>
        (m, "Foo", "")
    .def(py::init<>([](
    int X = 0, int Y = 1)
    {
        auto r = std::make_unique<Foo>();
        r->X = X;
        r->Y = Y;
        return r;
    })
    , py::arg("x") = 0, py::arg("y") = 1
    )
    .def_readwrite("x", &Foo::X, "")
    .def_readwrite("y", &Foo::Y, "")
    ;

Default constructor with named params#

litgen will automatically generate a default constructor with named params for structs. For classes, you can use options.class_create_default_named_ctor__regex. This constructor is generated only if the class/struct does not provide a default constructor.

See example below:

cpp_code = """
    enum class Options { A, B };

    // A constructor with named params will be created for FooClass, 
    // since options.class_create_default_named_ctor__regex  was filled
    class FooClass { 
      public:
        Options options = Options::A;
        int a = 1;
    };

    // A constructor with named params will be created for FooStruct
    struct FooStruct { int X = 0, Y = 1; };

    // FooStruct has a default constructor, so no constructor with named params will be generated
    struct FooStruct2 {
        FooStruct2();
        int X = 0, Y = 1;
    };
"""
options = litgen.LitgenOptions()
# options.struct_create_default_named_ctor__regex = r".*"
options.class_create_default_named_ctor__regex = r".*"
litgen_demo.demo(options, cpp_code)
    enum class Options { A, B };

    // A constructor with named params will be created for FooClass, 
    // since options.class_create_default_named_ctor__regex  was filled
    class FooClass { 
      public:
        Options options = Options::A;
        int a = 1;
    };

    // A constructor with named params will be created for FooStruct
    struct FooStruct { int X = 0, Y = 1; };

    // FooStruct has a default constructor, so no constructor with named params will be generated
    struct FooStruct2 {
        FooStruct2();
        int X = 0, Y = 1;
    };
class Options(enum.Enum):
    a = enum.auto() # (= 0)
    b = enum.auto() # (= 1)

class FooClass:
    """ A constructor with named params will be created for FooClass,
     since options.class_create_default_named_ctor__regex  was filled
    """
    options: Options = Options.a
    a: int = 1
    def __init__(self, options: Options = Options.a, a: int = 1) -> None:
        """Auto-generated default constructor with named params"""
        pass

class FooStruct:
    """ A constructor with named params will be created for FooStruct"""
    x: int = 0
    y: int = 1
    def __init__(self, x: int = 0, y: int = 1) -> None:
        """Auto-generated default constructor with named params"""
        pass

class FooStruct2:
    """ FooStruct has a default constructor, so no constructor with named params will be generated"""
    def __init__(self) -> None:
        pass
    x: int = 0
    y: int = 1


py::enum_<Options>(m, "Options", py::arithmetic(), "")
    .value("a", Options::A, "")
    .value("b", Options::B, "");


auto pyClassFooClass =
    py::class_<FooClass>
        (m, "FooClass", " A constructor with named params will be created for FooClass,\n since options.class_create_default_named_ctor__regex  was filled")
    .def(py::init<>([](
    Options options = Options::A, int a = 1)
    {
        auto r = std::make_unique<FooClass>();
        r->options = options;
        r->a = a;
        return r;
    })
    , py::arg("options") = Options::A, py::arg("a") = 1
    )
    .def_readwrite("options", &FooClass::options, "")
    .def_readwrite("a", &FooClass::a, "")
    ;


auto pyClassFooStruct =
    py::class_<FooStruct>
        (m, "FooStruct", "A constructor with named params will be created for FooStruct")
    .def(py::init<>([](
    int X = 0, int Y = 1)
    {
        auto r = std::make_unique<FooStruct>();
        r->X = X;
        r->Y = Y;
        return r;
    })
    , py::arg("x") = 0, py::arg("y") = 1
    )
    .def_readwrite("x", &FooStruct::X, "")
    .def_readwrite("y", &FooStruct::Y, "")
    ;


auto pyClassFooStruct2 =
    py::class_<FooStruct2>
        (m, "FooStruct2", "FooStruct has a default constructor, so no constructor with named params will be generated")
    .def(py::init<>())
    .def_readwrite("x", &FooStruct2::X, "")
    .def_readwrite("y", &FooStruct2::Y, "")
    ;

Expose protected member functions#

Relevant portion of the pybind11 manual.

Exposing protected member functions requires the creation of a “Publicist” helper class. litgen enables to automate this:

cpp_code = """
class A {
protected:
    int foo() const { return 42; }
};
"""

options = litgen.LitgenOptions()
options.class_expose_protected_methods__regex = "^A$"
litgen_demo.demo(options, cpp_code, show_pydef=True)
class A {
protected:
    int foo() const { return 42; }
};
class A:
    def __init__(self) -> None:
        """Autogenerated default constructor"""
        pass

    # <protected_methods>
    def foo(self) -> int:
        pass
    # </protected_methods>


auto pyClassA =
    py::class_<A>
        (m, "A", "")
    .def(py::init<>()) // implicit default constructor
    .def("foo",
        &A_publicist::foo)
    ;

// helper type for exposing protected functions
class A_publicist : public A
{
public:
    using A::foo;
};

Overriding virtual methods in Python#

Relevant portion of the pybind11 manual.

In order to override virtual methods in Python, you need to create of a trampoline class, which can be a bit cumbersome.

litgen can automate this: look at the pybind11 binding code, and at the glue code below.

cpp_code = """
    class Animal {
    public:
        virtual ~Animal() { }
        virtual std::string go(int n_times) = 0;
    };
"""

options = litgen.LitgenOptions()
options.class_override_virtual_methods_in_python__regex = "^Animal$"
litgen_demo.demo(options, cpp_code, show_pydef=True)
    class Animal {
    public:
        virtual ~Animal() { }
        virtual std::string go(int n_times) = 0;
    };
class Animal:
    def go(self, n_times: int) -> str: # overridable (pure virtual)
        pass
    def __init__(self) -> None:
        """Autogenerated default constructor"""
        pass


auto pyClassAnimal =
    py::class_<Animal, Animal_trampoline>
        (m, "Animal", "")
    .def(py::init<>()) // implicit default constructor
    .def("go",
        &Animal::go, py::arg("n_times"))
    ;

// helper type to enable overriding virtual methods in python
class Animal_trampoline : public Animal
{
public:
    using Animal::Animal;

    std::string go(int n_times) override
    {
        PYBIND11_OVERRIDE_PURE_NAME(
            std::string, // return type
            Animal, // parent class
            "go", // function name (python)
            go, // function name (c++)
            n_times // params
        );
    }
};

Combining virtual functions and inheritance#

Relevant portion of the pybind11 manual.

cpp_code = """
    class Animal {
    public:
        virtual std::string go(int n_times) = 0;
        virtual std::string name() { return "unknown"; }
    };

    class Dog : public Animal {
    public:
        std::string go(int n_times) override {
            std::string result;
            for (int i=0; i<n_times; ++i)
                result += bark() + " ";
            return result;
        }
        virtual std::string bark() { return "woof!"; }
    };
"""

options = litgen.LitgenOptions()
options.class_override_virtual_methods_in_python__regex = "^Animal$|^Dog$"
litgen_demo.demo(options, cpp_code, show_pydef=True)
    class Animal {
    public:
        virtual std::string go(int n_times) = 0;
        virtual std::string name() { return "unknown"; }
    };

    class Dog : public Animal {
    public:
        std::string go(int n_times) override {
            std::string result;
            for (int i=0; i<n_times; ++i)
                result += bark() + " ";
            return result;
        }
        virtual std::string bark() { return "woof!"; }
    };
class Animal:
    def go(self, n_times: int) -> str: # overridable (pure virtual)
        pass
    def name(self) -> str:             # overridable
        pass
    def __init__(self) -> None:
        """Autogenerated default constructor"""
        pass

class Dog(Animal):
    def go(self, n_times: int) -> str: # overridable
        pass
    def bark(self) -> str:             # overridable
        pass
    def __init__(self) -> None:
        """Autogenerated default constructor"""
        pass


auto pyClassAnimal =
    py::class_<Animal, Animal_trampoline>
        (m, "Animal", "")
    .def(py::init<>()) // implicit default constructor
    .def("go",
        &Animal::go, py::arg("n_times"))
    .def("name",
        &Animal::name)
    ;


auto pyClassDog =
    py::class_<Dog, Animal, Dog_trampoline>
        (m, "Dog", "")
    .def(py::init<>()) // implicit default constructor
    .def("go",
        &Dog::go, py::arg("n_times"))
    .def("bark",
        &Dog::bark)
    ;

// helper type to enable overriding virtual methods in python
class Animal_trampoline : public Animal
{
public:
    using Animal::Animal;

    std::string go(int n_times) override
    {
        PYBIND11_OVERRIDE_PURE_NAME(
            std::string, // return type
            Animal, // parent class
            "go", // function name (python)
            go, // function name (c++)
            n_times // params
        );
    }
    std::string name() override
    {
        PYBIND11_OVERRIDE_NAME(
            std::string, // return type
            Animal, // parent class
            "name", // function name (python)
            name // function name (c++)
        );
    }
};



// helper type to enable overriding virtual methods in python
class Dog_trampoline : public Dog
{
public:
    using Dog::Dog;

    std::string go(int n_times) override
    {
        PYBIND11_OVERRIDE_NAME(
            std::string, // return type
            Dog, // parent class
            "go", // function name (python)
            go, // function name (c++)
            n_times // params
        );
    }
    std::string bark() override
    {
        PYBIND11_OVERRIDE_NAME(
            std::string, // return type
            Dog, // parent class
            "bark", // function name (python)
            bark // function name (c++)
        );
    }
    std::string name() override
    {
        PYBIND11_OVERRIDE_NAME(
            std::string, // return type
            Dog, // parent class
            "name", // function name (python)
            name // function name (c++)
        );
    }
};

Operator overloading#

litgen is able to automatically transform C++ numerical operators into their corresponding dunder function in Python.

Overloading addition, substraction, etc.#

See example below:

cpp_code = """
        struct IntWrapper
        {
            int value;
            IntWrapper(int v) : value(v) {}

            // arithmetic operators
            IntWrapper operator+(IntWrapper b) { return IntWrapper{ value + b.value}; }
            IntWrapper operator-(IntWrapper b) { return IntWrapper{ value - b.value }; }

            // Unary minus operator
            IntWrapper operator-() { return IntWrapper{ -value }; }

            // Comparison operator
            bool operator<(IntWrapper b) { return value < b.value; }

            // Two overload of the += operator
            IntWrapper operator+=(IntWrapper b) { value += b.value; return *this; }
            IntWrapper operator+=(int b) { value += b; return *this; }

            // Two overload of the call operator, with different results
            int operator()(IntWrapper b) { return value * b.value + 2; }
            int operator()(int b) { return value * b + 3; }
        };
"""

options = litgen.LitgenOptions()
litgen_demo.demo(options, cpp_code, height=60)
        struct IntWrapper
        {
            int value;
            IntWrapper(int v) : value(v) {}

            // arithmetic operators
            IntWrapper operator+(IntWrapper b) { return IntWrapper{ value + b.value}; }
            IntWrapper operator-(IntWrapper b) { return IntWrapper{ value - b.value }; }

            // Unary minus operator
            IntWrapper operator-() { return IntWrapper{ -value }; }

            // Comparison operator
            bool operator<(IntWrapper b) { return value < b.value; }

            // Two overload of the += operator
            IntWrapper operator+=(IntWrapper b) { value += b.value; return *this; }
            IntWrapper operator+=(int b) { value += b; return *this; }

            // Two overload of the call operator, with different results
            int operator()(IntWrapper b) { return value * b.value + 2; }
            int operator()(int b) { return value * b + 3; }
        };
class IntWrapper:
    value: int
    def __init__(self, v: int) -> None:
        pass

    # arithmetic operators
    def __add__(self, b: IntWrapper) -> IntWrapper:
        pass
    @overload
    def __sub__(self, b: IntWrapper) -> IntWrapper:
        pass

    @overload
    def __neg__(self) -> IntWrapper:
        """ Unary minus operator"""
        pass

    def __lt__(self, b: IntWrapper) -> bool:
        """ Comparison operator"""
        pass

    # Two overload of the += operator
    @overload
    def __iadd__(self, b: IntWrapper) -> IntWrapper:
        pass
    @overload
    def __iadd__(self, b: int) -> IntWrapper:
        pass

    # Two overload of the call operator, with different results
    @overload
    def __call__(self, b: IntWrapper) -> int:
        pass
    @overload
    def __call__(self, b: int) -> int:
        pass


auto pyClassIntWrapper =
    py::class_<IntWrapper>
        (m, "IntWrapper", "")
    .def_readwrite("value", &IntWrapper::value, "")
    .def(py::init<int>(),
        py::arg("v"))
    .def("__add__",
        &IntWrapper::operator+, py::arg("b"))
    .def("__sub__",
        py::overload_cast<IntWrapper>(&IntWrapper::operator-), py::arg("b"))
    .def("__neg__",
        [](IntWrapper & self) { return self.operator-(); }, "Unary minus operator")
    .def("__lt__",
        &IntWrapper::operator<,
        py::arg("b"),
        "Comparison operator")
    .def("__iadd__",
        py::overload_cast<IntWrapper>(&IntWrapper::operator+=), py::arg("b"))
    .def("__iadd__",
        py::overload_cast<int>(&IntWrapper::operator+=), py::arg("b"))
    .def("__call__",
        py::overload_cast<IntWrapper>(&IntWrapper::operator()), py::arg("b"))
    .def("__call__",
        py::overload_cast<int>(&IntWrapper::operator()), py::arg("b"))
    ;

Overloading comparisons with the spaceship operator#

cpp_code = """
    struct Point
    {
        int x;
        int y;
        auto operator<=>(const Point&) const = default;
    };
"""
options = litgen.LitgenOptions()
litgen_demo.demo(options, cpp_code)
    struct Point
    {
        int x;
        int y;
        auto operator<=>(const Point&) const = default;
    };
class Point:
    x: int
    y: int
    def __lt__(self, param_0: Point) -> bool:
        """
        (C++ auto return type)
        """
        pass
    def __le__(self, param_0: Point) -> bool:
        """
        (C++ auto return type)
        """
        pass
    def __eq__(self, param_0: Point) -> bool:
        """
        (C++ auto return type)
        """
        pass
    def __ge__(self, param_0: Point) -> bool:
        """
        (C++ auto return type)
        """
        pass
    def __gt__(self, param_0: Point) -> bool:
        """
        (C++ auto return type)
        """
        pass
    def __init__(self, x: int = int(), y: int = int()) -> None:
        """Auto-generated default constructor with named params"""
        pass


auto pyClassPoint =
    py::class_<Point>
        (m, "Point", "")
    .def(py::init<>([](
    int x = int(), int y = int())
    {
        auto r = std::make_unique<Point>();
        r->x = x;
        r->y = y;
        return r;
    })
    , py::arg("x") = int(), py::arg("y") = int()
    )
    .def_readwrite("x", &Point::x, "")
    .def_readwrite("y", &Point::y, "")
    .def("__lt__",
        [](const Point & self, const Point & param_0) -> bool
        {
            auto cmp = [&self](auto&& other) -> bool {
                return self.operator<=>(other)  < 0;
            };

            return cmp(param_0);
        },
        py::arg("param_0"),
        "\n(C++ auto return type)")
    .def("__le__",
        [](const Point & self, const Point & param_0) -> bool
        {
            auto cmp = [&self](auto&& other) -> bool {
                return self.operator<=>(other)  <= 0;
            };

            return cmp(param_0);
        },
        py::arg("param_0"),
        "\n(C++ auto return type)")
    .def("__eq__",
        [](const Point & self, const Point & param_0) -> bool
        {
            auto cmp = [&self](auto&& other) -> bool {
                return self.operator<=>(other)  == 0;
            };

            return cmp(param_0);
        },
        py::arg("param_0"),
        "\n(C++ auto return type)")
    .def("__ge__",
        [](const Point & self, const Point & param_0) -> bool
        {
            auto cmp = [&self](auto&& other) -> bool {
                return self.operator<=>(other)  >= 0;
            };

            return cmp(param_0);
        },
        py::arg("param_0"),
        "\n(C++ auto return type)")
    .def("__gt__",
        [](const Point & self, const Point & param_0) -> bool
        {
            auto cmp = [&self](auto&& other) -> bool {
                return self.operator<=>(other)  > 0;
            };

            return cmp(param_0);
        },
        py::arg("param_0"),
        "\n(C++ auto return type)")
    ;

Dynamic attributes#

Relevant portion of the pybind11 manual.

The python class Pet below will be able to pick up new attributes dynamically:

cpp_code = """
struct Pet {
    Pet(const std::string &name) : name(name) { }
    std::string name;
};
"""
options = litgen.LitgenOptions()
options.class_dynamic_attributes__regex = "^Pet$"
litgen_demo.demo(options, cpp_code, show_pydef=True)
struct Pet {
    Pet(const std::string &name) : name(name) { }
    std::string name;
};
class Pet:
    def __init__(self, name: str) -> None:
        pass
    name: str


auto pyClassPet =
    py::class_<Pet>
        (m, "Pet", py::dynamic_attr(), "")
    .def(py::init<const std::string &>(),
        py::arg("name"))
    .def_readwrite("name", &Pet::name, "")
    ;

Copy and Deep copy#

Relevant portion of the pybind11 manual

See below for instructions on how to add support for copy and deepcopy.

cpp_code = """
    struct Foo { std::vector<int> values; };
    struct Foo2 { 
        Foo foo1 = Foo();
        Foo foo2 = Foo();
    };
"""

options = litgen.LitgenOptions()
options.class_copy__regex = "^Foo$|^Foo2$"
options.class_deep_copy__regex = "^Foo2$"
options.class_copy_add_info_in_stub = True
litgen_demo.demo(options, cpp_code)
    struct Foo { std::vector<int> values; };
    struct Foo2 { 
        Foo foo1 = Foo();
        Foo foo2 = Foo();
    };
class Foo:
    """
    (has support for copy.copy)
    """
    values: List[int]
    def __init__(self, values: List[int] = List[int]()) -> None:
        """Auto-generated default constructor with named params"""
        pass
class Foo2:
    """
    (has support for copy.copy and copy.deepcopy)
    """
    foo1: Foo = Foo()
    foo2: Foo = Foo()
    def __init__(self, foo1: Foo = Foo(), foo2: Foo = Foo()) -> None:
        """Auto-generated default constructor with named params"""
        pass


auto pyClassFoo =
    py::class_<Foo>
        (m, "Foo", "\n(has support for copy.copy)")
    .def(py::init<>([](
    std::vector<int> values = std::vector<int>())
    {
        auto r = std::make_unique<Foo>();
        r->values = values;
        return r;
    })
    , py::arg("values") = std::vector<int>()
    )
    .def_readwrite("values", &Foo::values, "")
    .def("__copy__",  [](const Foo &self) {
        return Foo(self);
    })    ;


auto pyClassFoo2 =
    py::class_<Foo2>
        (m, "Foo2", "\n(has support for copy.copy and copy.deepcopy)")
    .def(py::init<>([](
    Foo foo1 = Foo(), Foo foo2 = Foo())
    {
        auto r = std::make_unique<Foo2>();
        r->foo1 = foo1;
        r->foo2 = foo2;
        return r;
    })
    , py::arg("foo1") = Foo(), py::arg("foo2") = Foo()
    )
    .def_readwrite("foo1", &Foo2::foo1, "")
    .def_readwrite("foo2", &Foo2::foo2, "")
    .def("__copy__",  [](const Foo2 &self) {
        return Foo2(self);
    })
    .def("__deepcopy__",  [](const Foo2 &self, py::dict) {
        return Foo2(self);
    }, py::arg("memo"))    ;

Iterable classes#

It is possible to make custom container classes iterable in python. See example below:

cpp_code = """
class MyContainer
{
public:
    float operator[](int idx);

    // We need to have defined begin(), end(), and size() to make the class iterable
    iterator begin(); // This function is excluded from the bindings (see options.fn_exclude_by_name__regex)
    iterator end();   // This function is excluded from the bindings (see options.fn_exclude_by_name__regex)
    size_t size();    // This function is also published as __len__
private:
    std::vector<float> values;
};
"""

options = litgen.LitgenOptions()
options.class_iterables_infos.add_iterable_class("^MyContainer$", "float")
options.fn_exclude_by_name__regex = "^begin$|^end$"
litgen_demo.demo(options, cpp_code, show_pydef=True)
class MyContainer
{
public:
    float operator[](int idx);

    // We need to have defined begin(), end(), and size() to make the class iterable
    iterator begin(); // This function is excluded from the bindings (see options.fn_exclude_by_name__regex)
    iterator end();   // This function is excluded from the bindings (see options.fn_exclude_by_name__regex)
    size_t size();    // This function is also published as __len__
private:
    std::vector<float> values;
};
class MyContainer:
    def __getitem__(self, idx: int) -> float:
        pass

    # We need to have defined begin(), end(), and size() to make the class iterable
    def size(self) -> int:
        """ This function is also published as __len__"""
        pass
    def __init__(self) -> None:
        """Autogenerated default constructor"""
        pass
    def __iter__(self) -> Iterator[float]:
        pass
    def __len__(self) -> int:
        pass


auto pyClassMyContainer =
    py::class_<MyContainer>
        (m, "MyContainer", "")
    .def(py::init<>()) // implicit default constructor
    .def("__getitem__",
        &MyContainer::operator[], py::arg("idx"))
    .def("size",
        &MyContainer::size, "This function is also published as __len__")
    .def("__iter__", [](const MyContainer &v) { return py::make_iterator(v.begin(), v.end()); }, py::keep_alive<0, 1>())
    .def("__len__", [](const MyContainer &v) { return v.size(); })
    ;

Numeric C array members#

If a struct/class stores a numeric C array member, it will be exposed as a modifiable numpy array.

cpp_code = """
struct Foo {  int values[4]; };
"""
options = litgen.LitgenOptions()
# options.member_numeric_c_array_replace__regex = r".*"  # this is the default
litgen_demo.demo(options, cpp_code, show_pydef=True)
struct Foo {  int values[4]; };
class Foo:
    values: np.ndarray  # ndarray[type=int, size=4]
    def __init__(self) -> None:
        """Auto-generated default constructor"""
        pass


auto pyClassFoo =
    py::class_<Foo>
        (m, "Foo", "")
    .def(py::init<>()) // implicit default constructor 
    .def_property("values",
        [](Foo &self) -> pybind11::array
        {
            auto dtype = pybind11::dtype(pybind11::format_descriptor<int>::format());
            auto base = pybind11::array(dtype, {4}, {sizeof(int)});
            return pybind11::array(dtype, {4}, {sizeof(int)}, self.values, base);
        }, [](Foo& self) {},
        "")
    ;

Inner class or enum#

litgen handles inner classes or enum and correctly nest them in their host class:

cpp_code = """
    struct Foo
    {
        enum class Choice { A, B };
        int HandleChoice(Choice value = Choice::A) { return 0; }
    };
"""
options = litgen.LitgenOptions()
litgen_demo.demo(options, cpp_code)
    struct Foo
    {
        enum class Choice { A, B };
        int HandleChoice(Choice value = Choice::A) { return 0; }
    };
class Foo:
    class Choice(enum.Enum):
        a = enum.auto() # (= 0)
        b = enum.auto() # (= 1)
    def handle_choice(self, value: Foo.Choice = Foo.Choice.a) -> int:
        pass
    def __init__(self) -> None:
        """Auto-generated default constructor"""
        pass


auto pyClassFoo =
    py::class_<Foo>
        (m, "Foo", "");

{ // inner classes & enums of Foo
    py::enum_<Foo::Choice>(pyClassFoo, "Choice", py::arithmetic(), "")
        .value("a", Foo::Choice::A, "")
        .value("b", Foo::Choice::B, "");
} // end of inner classes & enums of Foo

pyClassFoo
    .def(py::init<>()) // implicit default constructor 
    .def("handle_choice",
        &Foo::HandleChoice, py::arg("value") = Foo::Choice::A)
    ;