# Write C++ Bindings

## Quick Start

pkpy provides a pybind11 compatible layer which allows users to do convenient bindings. Header files are located in the `include/pybind11` directory. Make sure you have added `-Iinclude` to your compiler flags.

To begin with, use `py::scoped_interpreter guard{}` to start the interpreter before using any Python objects. Or explicitly call `py::interpreter::initialize()` and `py::interpreter::finalize()`.

### module

    #include <pybind11/pybind11.h>
    namespace py = pybind11;

    PYBIND11_EMBEDDED_MODULE(example, m) {
        m.def("add", [](int a, int b) {
            return a + b;
        });

        auto math = m.def_submodule("math");
    }

### function

    int add(int a, int b) { return a + b; }

    int add(int a, int b, int c) { return a + b + c; }

    void register_function(py::module_& m)
    {
        m.def("add", py::overload_cast<int, int>(&add));

        // support function overload
        m.def("add", py::overload_cast<int, int, int>(&add));

        // bind with default arguments
        m.def("sub", [](int a, int b) {
            return a - b;
        }, py::arg("a") = 1, py::arg("b") = 2);

        // bind *args
        m.def("add", [](py::args args) {
            int sum = 0;
            for (auto& arg : args) {
                sum += arg.cast<int>();
            }
            return sum;
        });

        // bind **kwargs
        m.def("add", [](py::kwargs kwargs) {
            int sum = 0;
            for (auto item : kwargs) {
                sum += item.second.cast<int>();
            }
            return sum;
        });
    }

### class

    struct Point
    {
        const int x;
        int y;

    public:
        Point() : x(0), y(0) {}

        Point(int x, int y) : x(x), y(y) {}

        Point(const Point& p) : x(p.x), y(p.y) {}

        std::string stringfy() const {
            return "(" + std::to_string(x) + ", " + std::to_string(y) + ")";
        }
    };

    struct Point3D : Point
    {
    private:
        int z;

    public:
        Point3D(int x, int y, int z) : Point(x, y), z(z) {}

        int get_z() const { return z; }

        void set_z(int z) { this->z = z; }
    };

    void bind_class(py::module_& m)
    {
        py::class_<Point>(m, "Point")
            .def(py::init<>())
            .def(py::init<int, int>())
            .def(py::init<const Point&>())
            .def_readonly("x", &Point::x)
            .def_readwrite("y", &Point::y)
            .def("__str__", &Point::stringfy);

        // only support single inheritance
        py::class_<Point3D, Point>(m, "Point3D", py::dynamic_attr())
            .def(py::init<int, int, int>())
            .def_property("z", &Point3D::get_z, &Point3D::set_z);

        // dynamic_attr will enable the dict of bound class
    }

### operators

    #include <pybind11/operators.h>
    namespace py = pybind11;

    struct Int {
        int value;

        Int(int value) : value(value) {}

        Int operator+(const Int& other) const {
            return Int(value + other.value);
        }

        Int operator-(const Int& other) const {
            return Int(value - other.value);
        }

        bool operator==(const Int& other) const {
            return value == other.value;
        }

        bool operator!=(const Int& other) const {
            return value != other.value;
        }
    };

    void bind_operators(py::module_& m)
    {
        py::class_<Int>(m, "Int")
            .def(py::init<int>())
            .def(py::self + py::self)
            .def(py::self - py::self)
            .def(py::self == py::self)
            .def(py::self != py::self);
            // other operators are similar
    }

### py::object

`py::object` is just simple wrapper around `PyVar`. It supports some convenient methods to interact with Python objects.

here are some common methods:

    obj.attr("x"); // access attribute
    obj[1]; // access item

    obj.is_none(); // same as obj is None in Python
    obj.is(obj2); // same as obj is obj2 in Python

    // operators
    obj + obj2; // same as obj + obj2 in Python
    // ...
    obj == obj2; // same as obj == obj2 in Python
    // ...

    obj(...); // same as obj.__call__(...)

    py::cast(obj); // cast to Python object
    obj.cast<T>; // cast to C++ type

    py::type::of(obj); // get type of obj
    py::type::of<T>(); // get type of T, if T is registered

you can also create some builtin objects with their according wrappers:

    py::bool_ b = {true};
    py::int_ i = {1};
    py::float_ f = {1.0};
    py::str s = {"hello"};
    py::list l = {1, 2, 3};
    py::tuple t = {1, 2, 3};
    // ...

## More Examples

More examples please see the test folder in the GSoC repository. All tested features are supported.

## Limits and Comparison

This is a feature list of pybind11 for pocketpy. It lists all completed and pending features. It also lists the features that cannot be implemented in the current version of pocketpy.

### Function

- Function overloading
- Return value policy
- is_prepend
- `*args` and `**kwargs`
- Keep-alive
- Call Guard
- Default arguments
- Keyword-Only arguments
- Positional-Only arguments
- Allow/Prohibiting None arguments

### Class

- Creating bindings for a custom type
- Binding lambda functions
- Dynamic attributes
- Inheritance and automatic downcasting
- Enumerations and internal types
- Instance and static fields

> Binding static fields may never be implemented in pocketpy because it requires a metaclass, which is a heavy and infrequently used feature.

### Exceptions

Need further discussion.

### Smart pointers

- std::shared_ptr
- std::unique_ptr
- Custom smart pointers

### Type conversions

- Python built-in types
- STL Containers
- Functional
- Chrono

### Python C++ interface

Need further discussion.

- `object`
- `none`
- `type`
- `bool_`
- `int_`
- `float_`
- `str`
- `bytes`
- `bytearray`
- `tuple`
- `list`
- `set`
- `dict`
- `slice`
- `iterable`
- `iterator`
- `function`
- `buffer`
- `memoryview`
- `capsule`

### Miscellaneous

- Global Interpreter Lock (GIL)
- Binding sequence data types, iterators, the slicing protocol, etc.
- Convenient operators binding

### Differences between CPython and pocketpy

- only `add`, `sub` and `mul` have corresponding right versions in pocketpy. So if you bind `int() >> py::self`, it will has no effect in pocketpy.

- `__new__` and `__del__` are not supported in pocketpy.

- in-place operators, such as `+=`, `-=`, `*=`, etc., are not supported in pocketpy.

- the return value of `globals` is immutable in pocketpy.