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, "")
;
auto pyClassFoo =
nb::class_<Foo>
(m, "Foo", "")
.def("__init__", [](Foo * self, int X = 0, int Y = 1)
{
new (self) Foo(); // placement new
auto r = self;
r->X = X;
r->Y = Y;
},
nb::arg("x") = 0, nb::arg("y") = 1
)
.def_rw("x", &Foo::X, "")
.def_rw("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
auto pyEnumOptions =
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, "")
;
auto pyEnumOptions =
nb::enum_<Options>(m, "Options", nb::is_arithmetic(), "")
.value("a", Options::A, "")
.value("b", Options::B, "");
auto pyClassFooClass =
nb::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("__init__", [](FooClass * self, Options options = Options::A, int a = 1)
{
new (self) FooClass(); // placement new
auto r = self;
r->options = options;
r->a = a;
},
nb::arg("options") = Options::A, nb::arg("a") = 1
)
.def_rw("options", &FooClass::options, "")
.def_rw("a", &FooClass::a, "")
;
auto pyClassFooStruct =
nb::class_<FooStruct>
(m, "FooStruct", "A constructor with named params will be created for FooStruct")
.def("__init__", [](FooStruct * self, int X = 0, int Y = 1)
{
new (self) FooStruct(); // placement new
auto r = self;
r->X = X;
r->Y = Y;
},
nb::arg("x") = 0, nb::arg("y") = 1
)
.def_rw("x", &FooStruct::X, "")
.def_rw("y", &FooStruct::Y, "")
;
auto pyClassFooStruct2 =
nb::class_<FooStruct2>
(m, "FooStruct2", "FooStruct has a default constructor, so no constructor with named params will be generated")
.def(nb::init<>())
.def_rw("x", &FooStruct2::X, "")
.def_rw("y", &FooStruct2::Y, "")
;
Expose protected member functions#
Relevant portion of the pybind11 manual and of the nanobind 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)
;
auto pyClassA =
nb::class_<A>
(m, "A", "")
.def(nb::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 and of the nanobind 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"))
;
auto pyClassAnimal =
nb::class_<Animal, Animal_trampoline>
(m, "Animal", "")
.def(nb::init<>()) // implicit default constructor
.def("go",
&Animal::go, nb::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
);
}
};
Note: in the case of nanobind, the glue code will differ a bit. It is shown below:
options.bind_library = litgen.BindLibraryType.nanobind
generated_code = litgen.generate_code(options, cpp_code)
litgen_demo.show_cpp_code(generated_code.glue_code, "Glue code with nanobind")
// helper type to enable overriding virtual methods in python
class Animal_trampoline : public Animal
{
public:
NB_TRAMPOLINE(Animal, 1);
std::string go(int n_times) override
{
NB_OVERRIDE_PURE_NAME(
"go", // function name (python)
go, // function name (c++)
n_times // params
);
}
};
Combining virtual functions and inheritance#
Relevant portion of the pybind11 manual and of the nanobind 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)
;
auto pyClassAnimal =
nb::class_<Animal, Animal_trampoline>
(m, "Animal", "")
.def(nb::init<>()) // implicit default constructor
.def("go",
&Animal::go, nb::arg("n_times"))
.def("name",
&Animal::name)
;
auto pyClassDog =
nb::class_<Dog, Animal, Dog_trampoline>
(m, "Dog", "")
.def(nb::init<>()) // implicit default constructor
.def("go",
&Dog::go, nb::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++)
);
}
};
Note: in the case of nanobind, the glue code will differ a bit. It is shown below:
options.bind_library = litgen.BindLibraryType.nanobind
generated_code = litgen.generate_code(options, cpp_code)
litgen_demo.show_cpp_code(generated_code.glue_code, "Glue code with nanobind")
// helper type to enable overriding virtual methods in python
class Animal_trampoline : public Animal
{
public:
NB_TRAMPOLINE(Animal, 2);
std::string go(int n_times) override
{
NB_OVERRIDE_PURE_NAME(
"go", // function name (python)
go, // function name (c++)
n_times // params
);
}
std::string name() override
{
NB_OVERRIDE_NAME(
"name", // function name (python)
name // function name (c++)
);
}
};
// helper type to enable overriding virtual methods in python
class Dog_trampoline : public Dog
{
public:
NB_TRAMPOLINE(Dog, 3);
std::string go(int n_times) override
{
NB_OVERRIDE_NAME(
"go", // function name (python)
go, // function name (c++)
n_times // params
);
}
std::string bark() override
{
NB_OVERRIDE_NAME(
"bark", // function name (python)
bark // function name (c++)
);
}
std::string name() override
{
NB_OVERRIDE_NAME(
"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"))
;
auto pyClassIntWrapper =
nb::class_<IntWrapper>
(m, "IntWrapper", "")
.def_rw("value", &IntWrapper::value, "")
.def(nb::init<int>(),
nb::arg("v"))
.def("__add__",
&IntWrapper::operator+, nb::arg("b"))
.def("__sub__",
nb::overload_cast<IntWrapper>(&IntWrapper::operator-), nb::arg("b"))
.def("__neg__",
[](IntWrapper & self) { return self.operator-(); }, "Unary minus operator")
.def("__lt__",
&IntWrapper::operator<,
nb::arg("b"),
"Comparison operator")
.def("__iadd__",
nb::overload_cast<IntWrapper>(&IntWrapper::operator+=), nb::arg("b"))
.def("__iadd__",
nb::overload_cast<int>(&IntWrapper::operator+=), nb::arg("b"))
.def("__call__",
nb::overload_cast<IntWrapper>(&IntWrapper::operator()), nb::arg("b"))
.def("__call__",
nb::overload_cast<int>(&IntWrapper::operator()), nb::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)")
;
auto pyClassPoint =
nb::class_<Point>
(m, "Point", "")
.def("__init__", [](Point * self, int x = int(), int y = int())
{
new (self) Point(); // placement new
auto r = self;
r->x = x;
r->y = y;
},
nb::arg("x") = int(), nb::arg("y") = int()
)
.def_rw("x", &Point::x, "")
.def_rw("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);
},
nb::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);
},
nb::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);
},
nb::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);
},
nb::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);
},
nb::arg("param_0"),
"\n(C++ auto return type)")
;
Dynamic attributes#
Relevant portion of the pybind11 manual and of the nanobind 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, "")
;
auto pyClassPet =
nb::class_<Pet>
(m, "Pet", nb::dynamic_attr(), "")
.def(nb::init<const std::string &>(),
nb::arg("name"))
.def_rw("name", &Pet::name, "")
;
Copy and Deep copy#
Relevant portion of the pybind11 manual. The principle is the same for nanobind.
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")) ;
auto pyClassFoo =
nb::class_<Foo>
(m, "Foo", "\n(has support for copy.copy)")
.def("__init__", [](Foo * self, std::vector<int> values = std::vector<int>())
{
new (self) Foo(); // placement new
auto r = self;
r->values = values;
},
nb::arg("values") = std::vector<int>()
)
.def_rw("values", &Foo::values, "")
.def("__copy__", [](const Foo &self) {
return Foo(self);
}) ;
auto pyClassFoo2 =
nb::class_<Foo2>
(m, "Foo2", "\n(has support for copy.copy and copy.deepcopy)")
.def("__init__", [](Foo2 * self, Foo foo1 = Foo(), Foo foo2 = Foo())
{
new (self) Foo2(); // placement new
auto r = self;
r->foo1 = foo1;
r->foo2 = foo2;
},
nb::arg("foo1") = Foo(), nb::arg("foo2") = Foo()
)
.def_rw("foo1", &Foo2::foo1, "")
.def_rw("foo2", &Foo2::foo2, "")
.def("__copy__", [](const Foo2 &self) {
return Foo2(self);
})
.def("__deepcopy__", [](const Foo2 &self, nb::dict) {
return Foo2(self);
}, nb::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(); })
;
auto pyClassMyContainer =
nb::class_<MyContainer>
(m, "MyContainer", "")
.def(nb::init<>()) // implicit default constructor
.def("__getitem__",
&MyContainer::operator[], nb::arg("idx"))
.def("size",
&MyContainer::size, "This function is also published as __len__")
.def("__iter__", [](const MyContainer &v) {
return nb::make_iterator(nb::type<MyContainer>(), "iterator", v.begin(), v.end());
}, nb::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) {},
"")
;
auto pyClassFoo =
nb::class_<Foo>
(m, "Foo", "")
.def(nb::init<>()) // implicit default constructor
.def_prop_ro("values",
[](Foo &self) -> nb::ndarray<int, nb::numpy, nb::shape<4>, nb::c_contig>
{
return self.values;
},
"")
;
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
auto pyEnumChoice =
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)
;
auto pyClassFoo =
nb::class_<Foo>
(m, "Foo", "");
{ // inner classes & enums of Foo
auto pyEnumChoice =
nb::enum_<Foo::Choice>(pyClassFoo, "Choice", nb::is_arithmetic(), "")
.value("a", Foo::Choice::A, "")
.value("b", Foo::Choice::B, "");
} // end of inner classes & enums of Foo
pyClassFoo
.def(nb::init<>()) // implicit default constructor
.def("handle_choice",
&Foo::HandleChoice, nb::arg("value") = Foo::Choice::A)
;