Using C++ in Cython

Overview

Cython has native support for most of the C++ language. Specifically:

  • C++ objects can bedynamically allocated withnew anddel keywords.

  • C++ objects can bestack-allocated.

  • C++ classes can be declared with the new keywordcppclass.

  • Templated classes and functions are supported.

  • Overloaded functions are supported.

  • Overloading of C++ operators (such asoperator+,operator[], …) is supported.

Procedure Overview

The general procedure for wrapping a C++ file can now be described as follows:

  • Specify C++ language in asetup.py script or locally in a source file.

  • Create one or more.pxd files withcdefexternfrom blocks and(if existing) the C++ namespace name. In these blocks:

    • declare classes ascdefcppclass blocks

    • declare public names (variables, methods and constructors)

  • cimport them in one or more extension modules (.pyx files).

A simple Tutorial

An example C++ API

Here is a tiny C++ API which we will use as an example throughout thisdocument. Let’s assume it will be in a header file calledRectangle.h:

#ifndef RECTANGLE_H#define RECTANGLE_Hnamespaceshapes{classRectangle{public:intx0,y0,x1,y1;Rectangle();Rectangle(intx0,inty0,intx1,inty1);~Rectangle();intgetArea();voidgetSize(int*width,int*height);voidmove(intdx,intdy);};}#endif

and the implementation in the file calledRectangle.cpp:

#include<iostream>#include"Rectangle.h"namespaceshapes{// Default constructorRectangle::Rectangle(){}// Overloaded constructorRectangle::Rectangle(intx0,inty0,intx1,inty1){this->x0=x0;this->y0=y0;this->x1=x1;this->y1=y1;}// DestructorRectangle::~Rectangle(){}// Return the area of the rectangleintRectangle::getArea(){return(this->x1-this->x0)*(this->y1-this->y0);}// Get the size of the rectangle.// Put the size in the pointer argsvoidRectangle::getSize(int*width,int*height){(*width)=x1-x0;(*height)=y1-y0;}// Move the rectangle by dx dyvoidRectangle::move(intdx,intdy){this->x0+=dx;this->y0+=dy;this->x1+=dx;this->y1+=dy;}}

This is pretty dumb, but should suffice to demonstrate the steps involved.

Declaring a C++ class interface

The procedure for wrapping a C++ class is quite similar to that for wrappingnormal C structs, with a couple of additions. Let’s start here by creating thebasiccdefexternfrom block:

cdefexternfrom"Rectangle.h"namespace"shapes":

This will make the C++ class def for Rectangle available. Note the namespace declaration.Namespaces are simply used to make the fully qualified name of the object,and can be nested (e.g."outer::inner") or even refer toclasses (e.g."namespace::MyClass to declare static members on MyClass).

Declare class with cdef cppclass

Now, let’s add the Rectangle class to this extern from block - just copy theclass name from Rectangle.h and adjust for Cython syntax, so now it becomes:

cdefexternfrom"Rectangle.h"namespace"shapes":cdefcppclassRectangle:

Add public attributes

We now need to declare the attributes and methods for use on Cython. We put those declarationsin a file calledRectangle.pxd. You can see it as a header filewhich is readable by Cython:

cdefexternfrom"Rectangle.cpp":pass# Declare the class with cdefcdefexternfrom"Rectangle.h"namespace"shapes":cdefcppclassRectangle:Rectangle()except+Rectangle(int,int,int,int)except+intx0,y0,x1,y1intgetArea()voidgetSize(int*width,int*height)voidmove(int,int)

Note that the constructor is declared as “except +”. If the C++ code orthe initial memory allocation raises an exception due to a failure, thiswill let Cython safely raise an appropriate Python exception instead(see below). Without this declaration, C++ exceptions originating fromthe constructor will not be handled by Cython.

We use the lines:

cdefexternfrom"Rectangle.cpp":pass

to include the C++ code fromRectangle.cpp. It is also possible to specify tosetuptools thatRectangle.cpp is a source. To do that, you can add this directive at thetop of the.pyx (not.pxd) file:

# distutils: sources = Rectangle.cpp

Note that when you usecdefexternfrom, the path that you specify is relative to the currentfile, but if you use the distutils directive, the path is relative to thesetup.py. If you want to discover the path of the sources whenrunning thesetup.py, you can use thealiases argumentof thecythonize() function.

Declare a var with the wrapped C++ class

We’ll create a.pyx file namedrect.pyx to build our wrapper. We’reusing a name other thanRectangle, but if you prefer giving the same nameto the wrapper as the C++ class, see the section onresolving naming conflicts.

Within, we use cdef to declare a var of the class with the C++new statement:

# distutils: language = c++fromRectanglecimportRectangledefmain():rec_ptr=newRectangle(1,2,3,4)# Instantiate a Rectangle object on the heaptry:rec_area=rec_ptr.getArea()finally:delrec_ptr# delete heap allocated objectcdefRectanglerec_stack# Instantiate a Rectangle object on the stack

The line:

# distutils: language = c++

is to indicate to Cython that this.pyx file has to be compiled to C++.

It’s also possible to declare a stack allocated object, as long as it hasa “default” constructor:

cdefexternfrom"Foo.h":cdefcppclassFoo:Foo()deffunc():cdefFoofoo...

See the section on thecpp_locals directive for a wayto avoid requiring a nullary/default constructor.

Note that, like C++, if the class has only one constructor and itis a nullary one, it’s not necessary to declare it.

Create Cython wrapper class

At this point, we have exposed into our pyx file’s namespace the interfaceof the C++ Rectangle type. Now, we need to make this accessible fromexternal Python code (which is our whole point).

Common programming practice is to create a Cython extension type whichholds a C++ instance as an attribute and create a bunch offorwarding methods. So we can implement the Python extension type as:

# distutils: language = c++fromRectanglecimportRectangle# Create a Cython extension type which holds a C++ instance# as an attribute and create a bunch of forwarding methods# Python extension type.cdefclassPyRectangle:cdefRectanglec_rect# Hold a C++ instance which we're wrappingdef__init__(self,intx0,inty0,intx1,inty1):self.c_rect=Rectangle(x0,y0,x1,y1)defget_area(self):returnself.c_rect.getArea()defget_size(self):cdefintwidth,heightself.c_rect.getSize(&width,&height)returnwidth,heightdefmove(self,dx,dy):self.c_rect.move(dx,dy)

And there we have it. From a Python perspective, this extension type will lookand feel just like a natively defined Rectangle class.It should be noted that if you want to giveattribute access, you could just implement some properties:

# distutils: language = c++fromRectanglecimportRectanglecdefclassPyRectangle:cdefRectanglec_rectdef__init__(self,intx0,inty0,intx1,inty1):self.c_rect=Rectangle(x0,y0,x1,y1)defget_area(self):returnself.c_rect.getArea()defget_size(self):cdefintwidth,heightself.c_rect.getSize(&width,&height)returnwidth,heightdefmove(self,dx,dy):self.c_rect.move(dx,dy)# Attribute access@propertydefx0(self):returnself.c_rect.x0@x0.setterdefx0(self,x0):self.c_rect.x0=x0# Attribute access@propertydefx1(self):returnself.c_rect.x1@x1.setterdefx1(self,x1):self.c_rect.x1=x1# Attribute access@propertydefy0(self):returnself.c_rect.y0@y0.setterdefy0(self,y0):self.c_rect.y0=y0# Attribute access@propertydefy1(self):returnself.c_rect.y1@y1.setterdefy1(self,y1):self.c_rect.y1=y1

Cython initializes C++ class attributes of a cdef class using the nullary constructor.If the class you’re wrapping does not have a nullary constructor, you must store a pointerto the wrapped class and manually allocate and deallocate it. Alternatively, thecpp_locals directive avoids the need for the pointer and only initializes theC++ class attribute when it is assigned to.A convenient and safe place to do so is in the__cinit__ and__dealloc__ methodswhich are guaranteed to be called exactly once upon creation and deletion of the Pythoninstance.

# distutils: language = c++fromRectanglecimportRectanglecdefclassPyRectangle:cdefRectangle*c_rect# hold a pointer to the C++ instance which we're wrappingdef__cinit__(self):self.c_rect=newRectangle()def__init__(self,intx0,inty0,intx1,inty1):self.c_rect.x0=x0self.c_rect.y0=y0self.c_rect.x1=x1self.c_rect.y1=y1def__dealloc__(self):delself.c_rect

Compilation and Importing

To compile a Cython module, it is necessary to have asetup.py file:

fromsetuptoolsimportsetupfromCython.Buildimportcythonizesetup(ext_modules=cythonize("rect.pyx"))

Run$pythonsetup.pybuild_ext--inplace

To test it, open the Python interpreter:

>>>importrect>>>x0,y0,x1,y1=1,2,3,4>>>rect_obj=rect.PyRectangle(x0,y0,x1,y1)>>>print(dir(rect_obj))['__class__','__delattr__','__dir__','__doc__','__eq__','__format__','__ge__','__getattribute__','__gt__','__hash__','__init__','__init_subclass__','__le__','__lt__','__ne__','__new__','__reduce__','__reduce_ex__','__repr__','__setattr__','__setstate__','__sizeof__','__str__','__subclasshook__','get_area','get_size','move']

Advanced C++ features

We describe here all the C++ features that were not discussed in the above tutorial.

Overloading

Overloading is very simple. Just declare the method with different parametersand use any of them:

cdefexternfrom"Foo.h":cdefcppclassFoo:Foo(int)Foo(bool)Foo(int,bool)Foo(int,int)

Overloading operators

Cython uses C++ naming for overloading operators:

cdefexternfrom"foo.h":cdefcppclassFoo:Foo()Foooperator+(Foo)Foooperator-(Foo)intoperator*(Foo)intoperator/(int)intoperator*(int,Foo)# allows 1*Foo()# nonmember operators can also be specified outside the classdoubleoperator/(double,Foo)cdefFoofoo=newFoo()foo2=foo+foofoo2=foo-foox=foo*foo2x=foo/1x=foo[0]*foo2x=foo[0]/1x=1*foo[0]cdefdoubleyy=2.0/foo[0]

Note that if one haspointers to C++ objects, dereferencing must be doneto avoid doing pointer arithmetic rather than arithmetic on the objectsthemselves:

cdefFoo*foo_ptr=newFoo()foo=foo_ptr[0]+foo_ptr[0]x=foo_ptr[0]/2delfoo_ptr

Nested class declarations

C++ allows nested class declaration. Class declarations can also benested in Cython:

# distutils: language = c++cdefexternfrom"<vector>"namespace"std":cdefcppclassvector[T]:cppclassiterator:Toperator*()iteratoroperator++()bintoperator==(iterator)bintoperator!=(iterator)vector()voidpush_back(T&)T&operator[](int)T&at(int)iteratorbegin()iteratorend()cdefvector[int].iteratoriter#iter is declared as being of type vector<int>::iterator

Note that the nested class is declared with acppclass but without acdef,as it is already part of acdef declaration section.

C++ operators not compatible with Python syntax

Cython tries to keep its syntax as close as possible to standard Python.Because of this, certain C++ operators, like the preincrement++fooor the dereferencing operator*foo cannot be used with the samesyntax as C++. Cython provides functions replacing these operators ina special modulecython.operator. The functions provided are:

  • cython.operator.dereference for dereferencing.dereference(foo)will produce the C++ code*(foo)

  • cython.operator.preincrement for pre-incrementation.preincrement(foo)will produce the C++ code++(foo).Similarly forpredecrement,postincrement andpostdecrement.

  • cython.operator.comma for the comma operator.comma(a,b)will produce the C++ code((a),(b)).

These functions need to be cimported. Of course, one can use afrom...cimport...as to have shorter and more readable functions.For example:fromcython.operatorcimportdereferenceasderef.

For completeness, it’s also worth mentioningcython.operator.addresswhich can also be written&foo.

Templates

Cython uses a bracket syntax for templating. A simple example for wrapping C++ vector:

# distutils: language = c++# import dereference and increment operatorsfromcython.operatorcimportdereferenceasderef,preincrementasinccdefexternfrom"<vector>"namespace"std":cdefcppclassvector[T]:cppclassiterator:Toperator*()iteratoroperator++()bintoperator==(iterator)bintoperator!=(iterator)vector()voidpush_back(T&)T&operator[](int)T&at(int)iteratorbegin()iteratorend()cdefvector[int] *v=newvector[int]()cdefintiforiinrange(10):v.push_back(i)cdefvector[int].iteratorit=v.begin()whileit!=v.end():print(deref(it))inc(it)delv

Multiple template parameters can be defined as a list, such as[T,U,V]or[int,bool,char]. Optional template parameters can be indicatedby writing[T,U,V=*]. In the event that Cython needs to explicitlyreference the type of a default template parameter for an incomplete templateinstantiation, it will writeMyClass<T,U>::V, so if the class providesa typedef for its template parameters it is preferable to use that name here.

Template functions are defined similarly to class templates, withthe template parameter list following the function name:

# distutils: language = c++cdefexternfrom"<algorithm>"namespace"std":Tmax[T](Ta,Tb)print(max[long](3,4))print(max(1.5,2.5))# simple template argument deduction

Standard library

Most of the containers of the C++ Standard Library have been declaredin pxd files locatedin/Cython/Includes/libcpp.These containers are: deque, list, map, pair, queue, set, stack, vector.

For example:

# distutils: language = c++fromlibcpp.vectorcimportvectorcdefvector[int]vectcdefinti,xforiinrange(10):vect.push_back(i)foriinrange(10):print(vect[i])forxinvect:print(x)

The pxd filesin/Cython/Includes/libcppalso work as good examples on how to declare C++ classes.

The STL containers coerce from and to thecorresponding Python builtin types. The conversion is triggeredeither by an assignment to a typed variable (including typed functionarguments) or by an explicit cast, e.g.:

# cython: language_level=3# distutils: language=c++fromlibcpp.complexcimportcomplex,conjfromlibcpp.stringcimportstringfromlibcpp.vectorcimportvectorpy_bytes_object=b'The knights who say ni'py_unicode_object=u'Those who hear them seldom live to tell the tale.'cdefstrings=py_bytes_objectprint(s)# b'The knights who say ni'cdefstringcpp_string=<string>py_unicode_object.encode('utf-8')print(cpp_string)# b'Those who hear them seldom live to tell the tale.'cdefvector[int]vect=range(1,10,2)print(vect)# [1, 3, 5, 7, 9]cdefvector[string]cpp_strings=b'It is a good shrubbery'.split()print(cpp_strings[1])# b'is'# creates a python object, then convert it to C++ complexcomplex_val=1+2jcdefcomplex[double]c_value1=complex_valprint(c_value1)# (1+2j)# transforms a C++ object to another one without Python conversioncdefcomplex[double]c_value2=conj(c_value1)print(c_value2)# (1-2j)

The following coercions are available:

Python type =>

C++ type

=> Python type

bytes

std::string

bytes

iterable

std::vector

list

iterable

std::list

list

iterable

std::set

set

iterable

std::unordered_set

set

mapping

std::map

dict

mapping

std::unordered_map

dict

iterable (len 2)

std::pair

tuple (len 2)

complex

std::complex

complex

All conversions create a new container and copy the data into it.The items in the containers are converted to a corresponding typeautomatically, which includes recursively converting containersinside of containers, e.g. a C++ vector of maps of strings.

Be aware that the conversions do have some pitfalls, which aredetailed inthe troubleshooting section.

Iteration over stl containers (or indeed any class withbegin() andend() methods returning an object supporting incrementing, dereferencing,and comparison) is supported via thefor..in syntax (including in listcomprehensions). For example, one can write:

# distutils: language = c++fromlibcpp.vectorcimportvectordefmain():cdefvector[int]v=[4,6,5,10,3]cdefintvalueforvalueinv:print(value)return[x*xforxinvifx%2==0]

If the loop target variable is unspecified, an assignment from type*container.begin() is used fortype inference.

Note

Slicing stl containers is supported,you can doforxinmy_vector[:5]:... but unlike pointers slices,it will create a temporary Python object and iterate over it. Thusmaking the iteration very slow. You might want to avoid slicingC++ containers for performance reasons.

Simplified wrapping with default constructor

If your extension type instantiates a wrapped C++ class using the defaultconstructor (not passing any arguments), you may be able to simplify thelifecycle handling by tying it directly to the lifetime of the Python wrapperobject. Instead of a pointer attribute, you can declare an instance:

# distutils: language = c++fromlibcpp.vectorcimportvectorcdefclassVectorStack:cdefvector[int]vdefpush(self,x):self.v.push_back(x)defpop(self):ifself.v.empty():raiseIndexError()x=self.v.back()self.v.pop_back()returnx

Cython will automatically generate code that instantiates the C++ objectinstance when the Python object is created and deletes it when the Pythonobject is garbage collected.

Exceptions

Cython cannot throw C++ exceptions, or catch them with a try-except statement,but it is possible to declare a function as potentially raising an C++exception and converting it into a Python exception. For example,

cdefexternfrom"some_file.h":cdefintfoo()except+

This will translate try and the C++ error into an appropriate Python exception.The translation is performed according to the following table(thestd:: prefix is omitted from the C++ identifiers):

C++

Python

bad_alloc

MemoryError

bad_cast

TypeError

bad_typeid

TypeError

domain_error

ValueError

invalid_argument

ValueError

ios_base::failure

IOError

out_of_range

IndexError

overflow_error

OverflowError

range_error

ArithmeticError

underflow_error

ArithmeticError

(all others)

RuntimeError

Thewhat() message, if any, is preserved. Note that a C++ios_base_failure can denote EOF, but does not carry enough informationfor Cython to discern that, so watch out with exception masks on IO streams.

cdefintbar()except+MemoryError

This will catch any C++ error and raise a Python MemoryError in its place.(Any Python exception is valid here.)

Cython also supports using a custom exception handler. This is an advanced featurethat most users won’t need, but for those that do a full example follows:

cdefintraise_py_error()cdefintsomething_dangerous()except+raise_py_error

If something_dangerous raises a C++ exception then raise_py_error will becalled, which allows one to do custom C++ to Python error “translations.” Ifraise_py_error does not actually raise an exception a RuntimeError will beraised. This approach may also be used to manage custom Python exceptionscreated using the Python C API.

# raising.pxdcdefexternfrom"Python.h"nogil:ctypedefstructPyObjectcdefexternfrom*:"""    #include <Python.h>    #include <stdexcept>    #include <ios>    PyObject *CustomLogicError;    void create_custom_exceptions() {        CustomLogicError = PyErr_NewException("raiser.CustomLogicError", NULL, NULL);    }    void custom_exception_handler() {        try {            if (PyErr_Occurred()) {                ; // let the latest Python exn pass through and ignore the current one            } else {                throw;            }        }  catch (const std::logic_error& exn) {            // Add mapping of std::logic_error -> CustomLogicError            PyErr_SetString(CustomLogicError, exn.what());        } catch (...) {            PyErr_SetString(PyExc_RuntimeError, "Unknown exception");        }    }    class Raiser {        public:            Raiser () {}            void raise_exception() {                throw std::logic_error("Failure");            }    };    """cdefPyObject*CustomLogicErrorcdefvoidcreate_custom_exceptions()cdefvoidcustom_exception_handler()cdefcppclassRaiser:Raiser()noexceptvoidraise_exception()except+custom_exception_handler# raising.pyxcreate_custom_exceptions()PyCustomLogicError=<object>CustomLogicErrorcdefclassPyRaiser:cdefRaiserc_objdefraise_exception(self):self.c_obj.raise_exception()

The above example leverages Cython’s ability to includeverbatim C code in pxd files to create a new Python exception typeCustomLogicError and map it to the standard C++std::logic_error usingthecustom_exception_handler function. There is nothing special about usinga standard exception class here,std::logic_error could easily be replacedwith some new C++ exception type defined in this file. TheRaiser::raise_exception is marked with+custom_exception_handler toindicate that this function should be called whenever an exception is raised.The corresponding Python functionPyRaiser.raise_exception will raise aCustomLogicError whenever it is called. DefiningPyCustomLogicErrorallows other code to catch this exception, as shown below:

try:PyRaiser().raise_exception()exceptPyCustomLogicError:print("Caught the exception")

When defining custom exception handlers it is typically good to also includelogic to handle all the standard exceptions that Cython typically handles aslisted in the table above. The code for this standard exception handler can befoundhere.

If you want to usestd::exception_ptr then you cancimport it fromlibcpp.exception. That provides a special exception handlerexception_ptr_error_handler allowing you to declare a function:

cdefexternfrom"some_header":voidsome_function()except+exception_ptr_error_handler

The exception raised fromsome_function will always be anExceptionand the underlyingstd::exception_ptr can be retrieved withwrapped_exception_ptr_from_exception. This is a slightly niche use,butstd::exception_ptr is a useful way to safely store arbitrary C++exceptions for later.

There is also the special form:

cdefintraise_py_or_cpp()except+*

for those functions that may raise either a Python or a C++ exception.

Static member method

If the Rectangle class has a static member:

namespaceshapes{classRectangle{...public:staticvoiddo_something();};}

you can declare it using the Python @staticmethod decorator, i.e.:

cdefexternfrom"Rectangle.h"namespace"shapes":cdefcppclassRectangle:...@staticmethodvoiddo_something()

Declaring/Using References

Cython supports declaring lvalue references using the standardType& syntax.Note, however, that it is unnecessary to declare the arguments of externfunctions as references (const or otherwise) as it has no impact on thecaller’s syntax.

Scoped Enumerations

Cython supports scoped enumerations (enumclass) in C++ mode:

cdefenumclassCheese:cheddar=1camembert=2

As with “plain” enums, you may access the enumerators as attributes of the type.Unlike plain enums however, the enumerators are not visible to theenclosing scope:

cdefCheesec1=Cheese.cheddar# OKcdefCheesec2=cheddar# ERROR!

Optionally, you may specify the underlying type of a scoped enumeration.This is especially important when declaring an external scoped enumerationwith an underlying type:

cdefexternfrom"Foo.h":cdefenumclassSpam(unsignedint):x=10y=20...

Declaring an enum class ascpdef will create aPEP 435-style Python wrapper.

auto Keyword

Though Cython does not have anauto keyword, Cython local variablesnot explicitly typed withcdef are deduced from the types of the right handside ofall their assignments (see theinfer_typescompiler directive). This is particularly handywhen dealing with functions that return complicated, nested, templated types,e.g.:

cdefvector[int]v=...it=v.begin()

(Though of course thefor..in syntax is preferred for objects supportingthe iteration protocol.)

RTTI and typeid()

Cython has support for thetypeid(...) operator.

from cython.operator cimport typeid

Thetypeid(...) operator returns an object of the typeconsttype_info&.

If you want to store a type_info value in a C variable, you will need to store itas a pointer rather than a reference:

fromlibcpp.typeinfocimporttype_infocdefconsttype_info*info=&typeid(MyClass)

If an invalid type is passed totypeid, it will throw anstd::bad_typeidexception which is converted into aTypeError exception in Python.

An additional C++11-only RTTI-related class,std::type_index, is availableinlibcpp.typeindex.

Specify C++ language in setup.py

Instead of specifying the language and the sources in the source files, it ispossible to declare them in thesetup.py file:

fromsetuptoolsimportsetupfromCython.Buildimportcythonizesetup(ext_modules=cythonize("rect.pyx",# our Cython sourcesources=["Rectangle.cpp"],# additional source file(s)language="c++",# generate C++ code))

Cython will generate and compile therect.cpp file (fromrect.pyx), then it will compileRectangle.cpp(implementation of theRectangle class) and link both object filestogether intorect.so on Linux, orrect.pyd on windows,which you can then import in Python usingimportrect (if you forget to link theRectangle.o, you willget missing symbols while importing the library in Python).

Note that thelanguage option has no effect on user provided Extensionobjects that are passed intocythonize(). It is only used for modulesfound by file name (as in the example above).

Thecythonize() function in Cython versions up to 0.21 does notrecognize thelanguage option and it needs to be specified as anoption to anExtension that describes your extension and thatis then handled bycythonize() as follows:

fromsetuptoolsimportExtension,setupfromCython.Buildimportcythonizesetup(ext_modules=cythonize(Extension("rect",# the extension namesources=["rect.pyx","Rectangle.cpp"],# the Cython source and# additional C++ source fileslanguage="c++",# generate and compile C++ code)))

The options can also be passed directly from the source file, which isoften preferable (and overrides any global option). Starting withversion 0.17, Cython also allows passing external source files into thecythonize() command this way. Here is a simplified setup.py file:

fromsetuptoolsimportsetupfromCython.Buildimportcythonizesetup(name="rectangleapp",ext_modules=cythonize('*.pyx'),)

And in the .pyx source file, write this into the first comment block, beforeany source code, to compile it in C++ mode and link it statically against theRectangle.cpp code file:

# distutils: language = c++# distutils: sources = Rectangle.cpp

Note

When using distutils directives, the paths are relative to the workingdirectory of the setuptools run (which is usually the project root wherethesetup.py resides).

To compile manually (e.g. usingmake), thecython command-lineutility can be used to generate a C++.cpp file, and then compile itinto a python extension. C++ mode for thecython command is turnedon with the--cplus option.

cpp_locals directive

Thecpp_locals compiler directive is an experimental feature that makesC++ variables behave like normal Python object variables. With thisdirective they are only initialized at their first assignment, and thusthey no longer require a nullary constructor to be stack-allocated. Trying toaccess an uninitialized C++ variable will generate anUnboundLocalError(or similar) in the same way as a Python variable would. For example:

deffunction(dont_write):cdefSomeCppClassc# not initializedifdont_write:returnc.some_cpp_function()# UnboundLocalErrorelse:c=SomeCppClass(...)# initializedreturnc.some_cpp_function()# OK

Additionally, the directive avoids initializing temporary C++ objects beforethey are assigned, for cases where Cython needs to use such objects in itsown code-generation (often for return values of functions that can throwexceptions).

For extra speed, theinitializedcheck directive disables the check for anunbound-local. With this directive on, accessing a variable that has notbeen initialized will trigger undefined behaviour, and it is entirely the user’sresponsibility to avoid such access.

Thecpp_locals directive is currently implemented usingstd::optionaland thus requires a C++17 compatible compiler. DefiningCYTHON_USE_BOOST_OPTIONAL (as define for the C++ compiler) usesboost::optionalinstead (but is even more experimental and untested). The directive maycome with a memory and performance cost due to the need to store and checka boolean that tracks if a variable is initialized, but the C++ compiler shouldbe able to eliminate the check in most cases.

Caveats and Limitations

Access to C-only functions

Whenever generating C++ code, Cython generates declarations of and callsto functions assuming these functions are C++ (ie, not declared asextern"C"{...}. This is ok if the C functions have C++ entry points, but if they’re Conly, you will hit a roadblock. If you have a C++ Cython module needingto make calls to pure-C functions, you will need to write a small C++ shimmodule which:

  • includes the needed C headers in an extern “C” block

  • contains minimal forwarding functions in C++, each of which calls therespective pure-C function

C++ left-values

C++ allows functions returning a reference to be left-values. This is currentlynot supported in Cython.cython.operator.dereference(foo) is also notconsidered a left-value.