Introduction

cpptools is a modern C++ library

Licensing

This software is licensed under the MIT license license. See the LICENSE.txt file for details.

Features

Basic Usage

cd cpptools
conda env create -f cpptools-dev-requirements.yml
source activate cpptools-dev-requirements
mkdir build
cd build
cmake ..
make -j2
make cpp-test
make python-test
make install
cd examples
./hello_world
cd ..
cd benchmark
./benchmark_cpptools

On a windows machine this looks like:

cd cpptools
conda env create -f cpptools-dev-requirements.yml
call activate cpptools-dev-requirements
mkdir build
cd build
cmake .. -G"Visual Studio 15 2017 Win64" -DCMAKE_BUILD_TYPE=Release  ^
      -DDEPENDENCY_SEARCH_PREFIX="%CONDA_PREFIX%\Library" -DCMAKE_PREFIX_PATH="%CONDA_PREFIX%\Library"
call activate cpptools-dev-requirements
cmake --build . --target ALL_BUILD
cmake --build . --target python-test
cmake --build . --target cpp-test
cmake --build . --target install

Folder Structure

The generated project has the following folder structure

cpptools
  ├──azure-pipelines.yml                                # Ci script
  │
  ├──benchmark                                          # C++ benchmark code
  │   └── ...
  │
  ├──binder                                             # dockerfile for mybinder.org
  │   └── Dockerfile
  │
  ├──cmake                                              # Cmake script/modules
  │   └── ...
  │
  ├──CMakeLists.txt                                     # Main cmake list
  │
  ├──CONTRIBUTING.rst                                   # Introduction how to constribute
  │
  ├──cpptoolsConfig.cmake.in # Script to make find_package(...)
  │                                                     # work for this package
  │
  ├──cpptools.pc.in          # Packaging info
  │
  ├──cpptools-dev-requirements.yml # List of development conda dependencies
  │
  ├──docker                                              # dockerfile for dockerhub
  │   └── Dockerfile
  ├──docs                                               # Sources for sphinx documentation
  │   └── ...
  │
  ├──examples                                           # C++ examples
  │   └── ...
  │
  ├──include                                            # C++ include directory for this folder
  │   └── ...
  │
  ├──LICENCE.txt                                        # License file
  │
  ├──python                                             # Python binding source code
  │   └── ...
  │
  ├──README.rst                                         # Readme shown on github
  │
  ├──readthedocs.yml                                    # Entry point for automated
  │                                                     # documentation build on readthedocs.org
  │
  ├──recipe                                             # Folder for conda recipes
  │   └── ...
  │
  └──test                                               # Folder containing C++ unit tests
      └── ...

Unit Tests

We use doctest to create a benchmark for the C++ code.

The test subfolder contains all the code related to the C++ unit tests. In main.cpp implements the benchmarks runner, The unit tets are implemented in test_*.cpp. The test older looks like.

cpptools
├── ...
├── test
│   ├── CMakeLists.txt
│   ├── main.cpp
│   ├── test_cpptools_config.cpp
├── ...

Build System

There is a meta target called test_cpptools which bundles the build process of unit tests. Assuming you cmake-build directory is called bld the following will build all examples.

$ cd bld
$ make test_cpptools

To run the actual test you can use the target cpp_tests .. code-block:: shell

$ cd bld $ make cpp_tests

Adding New Tests

To add new tests just add a new cpp file to the test folder and update the CMakeLists.txt. Assuming we named the new cpp file test_my_new_feture.cpp, the relevant part in the CMakeLists.txt shall look like this:

# all tests
set(${PROJECT_NAME}_TESTS
    test_cpptools_config.cpp
    test_my_new_feture.cpp
)

After changing the CMakeLists.txt cmake needs to be rerun to configure the build again. After that make examples will build all examples including the freshly added examples.

$ cd bld
$ cmake .
$ make examples

Benchmark

We use gbench to create a benchmark for the C++ code.

The benchmark subfolder contains all the code related to the benchmarks. In main.cpp the actual benchmarks are implemented.

cpptools
├── ...
├── benchmark
│   ├── main.cpp
├── ...

Python Module

Folder Structure

We use pybind11 to create the python bindings. The python subfolder contains all the code related to the python bindings. The module/cpptools subfolder contains all the *.py files of the module. The src folder contains the *.cpp files used to export the C++ functionality to python via pybind11. The test folder contains all python tests.

cpptools
├── ...
├── python
│   ├── module
│   │   └── cpptools
│   │       ├── __init__.py
│   │       └── ...
│   ├── src
│   │   ├── CMakeLists.txt
│   │   ├── main.cpp
│   │   ├── def_build_config.cpp
│   │   ├── ...
│   └── test
│       ├── test_build_configuration.py
│       └── ...
└── ...

Build System

To build the python package use the python-module target.

make python-module

This will build the *.cpp files in the src folder and copy the folder module/cpptools folder to build location of the python module, namely ${CMAKE_BINARY_DIR}/python/module/ where ${CMAKE_BINARY_DIR} is the build directory.

Usage

After a successfully building and installing the python module can be imported like the following:

import cpptools

config = cpptools.BuildConfiguration
print(config.VERSION_MAJOR)

Run Python Tests

To run the python test suite use the python-test target:

make python-test

Adding New Python Functionality

We use pybind11 to export functionality from C++ to Python. pybind11 can create modules from C++ without the use of any *.py files. Nevertheless we prefer to have a regular Python package with a proper __init__.py. From the __init__.py we import all the C++ / pybind11 exported functionality from the build submodule named _cpptools. This allows us to add new functionality in different ways:

  • new functionality from c++ via pybind11

  • new puren python functionality

Add New Python Functionality from C++

To export functionality from C++ to python via pybind11 it is good practice to split functionality in multiple def_*.cpp files. This allow for readable code, and parallel builds. To add news functionality we create a new file, for example def_new_stuff.cpp.

#include "pybind11/pybind11.h"
#include "pybind11/numpy.h"

#include <iostream>
#include <numeric>

#define FORCE_IMPORT_ARRAY
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"

// our headers
#include "cpptools/cpptools.hpp"

namespace py = pybind11;


namespace cpptools {

    void def_new_stuff(py::module & m)
    {
        py::def('new_stuff',[](xt::pytensor<1,double> values){
            return values * 42.0;
        });
    }

}

Next we need to declare and call the def_new_stuff from main.cpp. To declare the function modify the following block in main.cpp

namespace cpptools {

    // ....
    // ....
    // ....

    // implementation in def_myclass.cpp
    void def_class(py::module & m);

    // implementation in def_myclass.cpp
    void def_build_config(py::module & m);

    // implementation in def.cpp
    void def_build_config(py::module & m);

    // implementation in def.cpp
    void def_build_config(py::module & m);

    // implementation in def_new_stuff.cpp
    void def_new_stuff(py::module & m);             // <- our new functionality

}

After declaring the function def_new_stuff, we can call def_new_stuff. We modify the PYBIND11_MODULE in code:main.cpp:

// Python Module and Docstrings
PYBIND11_MODULE(_cpptools , module)
{
    xt::import_numpy();

    module.doc() = R"pbdoc(
        _cpptools  python bindings

        .. currentmodule:: _cpptools

        .. autosummary::
           :toctree: _generate

           BuildConfiguration
           MyClass
           new_stuff
    )pbdoc";

    cpptools::def_build_config(module);
    cpptools::def_class(module);
    cpptools::def_new_stuff(module);  // <- our new functionality

    // make version string
    std::stringstream ss;
    ss<<CPPTOOLS_VERSION_MAJOR<<"."
      <<CPPTOOLS_VERSION_MINOR<<"."
      <<CPPTOOLS_VERSION_PATCH;
    module.attr("__version__") = ss.str();
}

We need to add this file to the CMakeLists.txt file at {cookiecutter.github_project_name}}/python/src/CMakeLists.txt The file needs to be passed as an argument to the pybind11_add_module function.

# add the python library
pybind11_add_module(${PY_MOD_LIB_NAME}
    main.cpp
    def_build_config.cpp
    def_myclass.cpp
    def_new_stuff.cpp  # <- our new functionality
)

Now we are ready to build the freshly added functionality.

make python-test

After a successful build we can use the new functionality from python.

import numpy as np
import cpptools

cpptools.new_stuff(numpy.arange(5), dtype='float64')

Add New Pure Python Functionality

To add new pure Python functionality, just add the desired function / classes to a new *.py file and put this file to the module/cpptools subfolder. After adding the new file, cmake needs to be rerun since we copy the content module/cpptools during the build process.

Adding New Python Tests

We use pytest as python test framework. To add new tests, just add new test_*.py files to the test subfolder. To run the actual test use the python-test target

make python-test

Examples

Folder Structure

The examples subfolder contains C++ examples which shall show the usage of the C++ library.

cpptools
├── ...
├── examples
│   ├── CMakeLists.txt
│   ├── hello_world.cpp
├── ...

Build System

There is a meta target called examples which bundles the build process of all cpp files in the folder examples in one target. Assuming you cmake-build directory is called bld the following will build all examples.

$ cd bld
$ make examples

Adding New Examples

To add new examples just add a new cpp file to the example folder and update the CMakeLists.txt. Assuming we named the new cpp file my_new_example.cpp, the relevant part in the CMakeLists.txt shall look like this:

# all examples
set(CPP_EXAMPLE_FILES
   hello_world.cpp
   my_new_example.cpp
)

After changing the CMakeLists.txt cmake needs to be rerun to configure the build again. After that make examples will build all examples including the freshly added examples.

$ cd bld
$ cmake .
$ make examples

Conda Recipe

The recipe subfolder contains all the code related to the conda recipe

project
├── ...
├── recipe
│   ├── bld.bat
│   ├── build.sh
│   ├── meta.tml
├── ...

cpptools API

Class Hierarchy

File Hierarchy

Full API

Namespaces

Namespace cpptools

Contents

Classes

Classes and Structs

Class MyClass
Class Documentation
class MyClass

Public Functions

inline MyClass(const uint64_t size)
inline void hello_world()

Defines

Define CPPTOOLS_CPPTOOLS_CONFIG_HPP
Define Documentation
CPPTOOLS_CPPTOOLS_CONFIG_HPP
Define CPPTOOLS_CPPTOOLS_HPP
Define Documentation
CPPTOOLS_CPPTOOLS_HPP
Define CPPTOOLS_CPPTOOLS_VERSION_MAJOR_HPP
Define Documentation
CPPTOOLS_CPPTOOLS_VERSION_MAJOR_HPP
Define CPPTOOLS_CPPTOOLS_VERSION_MINOR_HPP
Define Documentation
CPPTOOLS_CPPTOOLS_VERSION_MINOR_HPP
Define CPPTOOLS_CPPTOOLS_VERSION_PATCH_HPP
Define Documentation
CPPTOOLS_CPPTOOLS_VERSION_PATCH_HPP
Define CPPTOOLS_VERSION_MAJOR
Define Documentation
CPPTOOLS_VERSION_MAJOR
Define CPPTOOLS_VERSION_MINOR
Define Documentation
CPPTOOLS_VERSION_MINOR
Define CPPTOOLS_VERSION_PATCH
Define Documentation
CPPTOOLS_VERSION_PATCH

cpptools

cpptools package

Submodules

cpptools._cpptools module

_cpptools python bindings

class cpptools._cpptools.BuildConfiguration

Bases: pybind11_builtins.pybind11_object

This class show the compile/build configuration Of cpptools

DEBUG = True
VERSION_MAJOR = 0
VERSION_MINOR = 1
VERSION_PATCH = 0
class cpptools._cpptools.MyClass

Bases: pybind11_builtins.pybind11_object

hello_world(self: cpptools._cpptools.MyClass) None

Module contents

class cpptools.BuildConfiguration

Bases: pybind11_builtins.pybind11_object

This class show the compile/build configuration Of cpptools

DEBUG = True
VERSION_MAJOR = 0
VERSION_MINOR = 1
VERSION_PATCH = 0
class cpptools.MyClass

Bases: pybind11_builtins.pybind11_object

hello_world(self: cpptools._cpptools.MyClass) None
cpptools.pure_python()[source]

hello