Getting started

Starting example

Let us begin with a simple example:

// my_module.cpp

#include <c2py/c2py.hpp>

/** Some documentation
 *
 *  @param x First value
 *  @param y Second value
 *  @return The result
 */
int add(int x, int y) { return x + y; }

We just compile the C++ file with clang using the clair plugin as

# OS X
$ clang++ -fplugin=clair_c2py.dylib  my_module.cpp -std=c++20 -shared -o my_module.so `c2py_flags`

# Linux
$ clang++ -fplugin=clair_c2py.so     my_module.cpp -std=c++20 -shared -o my_module.so `c2py_flags`

Note

The compiler searches for the plugin in DYLD_LIBRARY_PATH (OS X) or LD_LIBRARY_PATH (linux).

That is it. The Python module is ready to be used:

>>> import my_module as M
>>> M.add(1,2)
3

The generation (and compilation) of the C++/Python binding code is automatically done by the clang compiler.

The C++ documentation is also automatically transformed into standard numpydoc format.

>>> help(M.add)
    add(...)
        Dispatched C++ function
        [1]  (x: int, y: int) -> int

           Some documentation

           Parameters
           ----------

           x:
              First value
           y:
              Second value

           Returns
           -------

           The result

What happened ?

In order to call C++ from Python, some binding code has to be generated. It is a piece of C++ code which adapts the C++ functions and classes to the C API of Python.

The clair_c2py compiler plugin automatizes this task, by modifying the compilation process of LLVM/clang. When compiling the C++ code my_module.cpp with it, the compiler

  1. parses my_module.cpp as usual (the first step of a compilation: check syntax and grammar, build the Abstract Syntax Tree or AST),

  2. generates the C++/Python bindings (by analyzing the AST) and writes them into my_module.wrap.cxx and

  3. compiles the whole code (source + bindings) into a Python module my_module.so.

Reusing the bindings without clang

While the generation of the bindings requires LLVM/clang and the clair plugin, they can be reused with any C++20 compiler. For example, with gcc:

$ g++  my_module.wrap.cxx -std=c++20 -shared -o my_module.so `c2py_flags`

(the binding file includes the original source file my_module.cpp).

Note

On some machines (OS X), beware that c2py must be compiled with gcc (in particular the same std C++ library). For any project beyond a simple demo, it is recommended to use CMake integration.

A typical workflow is therefore:

  • As the developer of the code, use clang with the clair plugin. Bindings are automatically regenerated when the C++ API change.

  • As a user of the code, just compile the bindings on any machine/compiler.

Section CMake integration shows how to use c2py and clair in a CMake project, including an example with a simple option to regenerate the bindings or not.

Note

Even though the bindings are readable C++ code, they are designed to be automatically generated, not written by hand. The c2py API is therefore not part of the user documentation.