
.. _autowrap:

Automated C++ header wrapping
=============================

robotpy-build can be told to parse C/C++ headers and automatically generate 
:std:doc:`pybind11 <pybind11:basics>` wrappers around the functions
and objects found in that header.

.. note:: We use a fork of `CppHeaderParser <https://github.com/robotpy/robotpy-cppheaderparser>`_
          to parse headers. We've improved it to handle many complicated modern
          C++ features, but if you run into problems please file a bug on github.

C++ Features
------------

robotpy-build uses a pure python C++ parser and macro processor to attempt to
parse header files. As a result, a full AST of the header files is not created.
This means particularly opaque code might confuse the parser, as robotpy-build
only receives the names, not the actual type information.

However, most basic features typically work without needing to coerce the
generator into working correctly, including:

* functions/methods (overloads, static, etc)
* public class variables
* protected class variables are (when possible) exported with a ``_`` prefix
* inheritance - detects and sets up Python object hierarchy automatically
* abstract classes - autogenerated code ensures they cannot be created directly
* virtual functions - automatically generates trampoline classes as described
  in the :ref:`pybind11 documentation <pybind11:overriding_virtuals>` so that
  python classes can override them
* final classes/methods - cannot be overridden from Python code
* Enumerations
* Global variables

Additionally, the following features are supported, but require some manual
intervention:

* C++ :ref:`class <class_templates>` and :ref:`function <function_templates>`
  templates

To tell the autogenerator to parse headers, you need to add a ``autogen_headers``
to your package in ``pyproject.toml``:

.. code-block:: toml

    [tool.robotpy-build.wrappers."MYPACKAGE".autogen_headers]
    demo = "demo.h"

That causes ``demo.h`` to be parsed and wrapped.

.. note:: If you're importing a large number of headers, the
          ``robotpy-build scan-headers`` tool can generate this for you
          automatically.

Documentation
-------------

robotpy-build will find doxygen documentation comments on many types of elements
and use sphinxify to translate them into python docstrings. All elements that
support documentation strings can have their docstrings set explicitly using 
a ``doc`` value in the YAML file.

.. code-block:: yaml

   classes:
     X:
       doc: Docstring for this class

.. _autowrap_parameters:

Parameters
----------

TODO

.. _autowrap_out_params:

Out parameters
~~~~~~~~~~~~~~

TODO

Conditional compilation
-----------------------

.. _class_templates:

Class templates
---------------

The code generator needs to be told which instantiations of the class
template to create. For a given class:

.. code-block:: c++

    template <typename T>
    struct TBasic
    {
        virtual ~TBasic() {}

        T getT() { return t; }
        virtual void setT(const T &t) { this->t = t; }

        T t;
    };

You need to tell the code generator two things about your class:

* Identify the template parameters in the class
* Declare explicit instantiations that you wish to expose, and their name

To cause a python class to be created called ``TBasicString`` which 
wraps ``TBasic<std::string>``:

.. code-block:: yaml

    classes:
      TBasic:
        template_params:
        - T
      
    templates:
      TBasicString:
        qualname: TBasic
        params:
        - std::string

.. _function_templates:

Function templates
------------------

The code generator needs to be told which instantiations of the function
template to create. For a given function:

.. code-block:: c++

    struct TClassWithFn
    {
        template <typename T>
        static T getT(T t)
        {
            return t;
        }
    };

The following would go in your YAML to create overloads callable from 
python that call ``bool getT(bool)`` and ``int getT(int)``.

.. code-block:: yaml

    classes:
      TClassWithFn:
        methods:
          getT:
            template_impls:
            - ["bool"]
            - ["int"]

Differing python and C++ function signatures
--------------------------------------------

Custom configuration of your functions allows you to define a more pythonic
API for your C++ classes.

Python only
~~~~~~~~~~~

This often comes up when the python type and a C++ type of a function parameter
or return value is different, or you want to omit a parameter. Just define a
lambda via ``cpp_code``:

.. code-block:: c++

  // original code
  int foo(int param1);

.. code-block:: yaml

  functions:
    foo:
      cpp_code:
        [](int param1) -> std::string {
          return std::to_string(param1);
        }

If you change the parameters, then you need to use ``param_override`` to
adjust the parameters. Let's say you wanted to remove 'param2':

.. code-block:: yaml

  functions:
    foo:
      param_override:
        param2:
          ignore: true

.. note:: When you change things like this, these inline definitions are
          *not* callable from C++, you need virtual functions for that.

Python and C++
~~~~~~~~~~~~~~

Let's say that you have a C++ virtual function ``void MyClass::foo(std::iostream &s)``.
Semantically, it's just returning a string. Because you really don't want to
wrap ``std::iostream``, you decide that the function should just return a
string in python.

Because this is a virtual function, you need to define a ``virtual_xform``
lambda that will take the original arguments, call the python API, then
return the original return type. Then when C++ code calls that virtual
function, it will call the xform function which will call your python API.

.. code-block:: yaml

  classes:
    MyClass:
      methods:
        foo:
          param_override:
            s:
              ignore: true
          cpp_code: |
            // python API
            [](MyClass * self) -> std::string {
              std::stringstream ss;
              self->foo(ss);
              return ss.str();
            }
          virtual_xform: |
            // C++ virtual function transformer
            [&](py::function &overload) {
              auto s = py::cast<std::string>(overload());
              ss << s;
            }
