# NumCpp: NumPy-style arrays for C++ without the Python runtime

> NumCpp is a header-only C++17/20/23 library that reimplements the NumPy array model, including NdArray, slicing and broadcasting. It suits engineers who want NumPy ergonomics inside compiled C++ code, and it is a poor fit for anyone who needs the Python ecosystem itself.

**dpilger26/NumCpp** — C++ implementation of the Python Numpy library

- Repository: https://github.com/dpilger26/NumCpp
- Website: https://dpilger26.github.io/NumCpp
- Stars: 3,964 · Forks: 575
- Language: C++
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/dpilger26-numcpp

## The gap NumCpp fills between Python prototyping and C++ deployment

Numerical work usually starts in Python because NumPy makes array code short and readable. Then it has to ship somewhere Python is unwelcome: a real-time loop, a plugin, a library with a C ABI, or a build system that already has enough moving parts. Rewriting np.array, slicing and broadcasting by hand in C++ is where the schedule goes. NumCpp targets exactly that rewrite. The README describes it as a templatized header-only C++ implementation of the Python NumPy library, and the quick start guide is organised as a NumPy-to-NumCpp translation table rather than as an API reference. That framing tells you the intended reader: someone who already knows NumPy and wants the same mental model in compiled code. It is not aimed at people who want to embed Python from C++, and it is not a replacement for SciPy's domain routines.

## NdArray, DataCube and the 2D-shaped core

The central type is nc::NdArray. According to the README, it is inherently a 2D array class, and 1D arrays are implemented as 1xN arrays. This is the single most important design fact in the library, because it leaks into everything: there is no separate vector type with different indexing rules, so a row vector and a column vector are both two-dimensional objects with a shape. There is also a DataCube class, described in the README as a convenience container for storing an array of 2D NdArrays, with the candid note that it has limited usefulness past a simple container. Take that at face value. If you need three-dimensional array semantics with the same richness as NumPy, the container exists but the README does not present it as a full n-dimensional abstraction. Slicing and broadcasting are provided in NumPy style: element access uses parentheses, a(2, 3), rather than brackets, and ranges are expressed with nc::Slice or brace initialisers, as in a({2, 5}, {5, 8}). Boolean masking works through a.putMask(a > 5, 0) instead of the assignment form a[a > 5] = 0, which is a genuine ergonomic difference from NumPy and one you will hit early if you port code mechanically.

## Installing NumCpp and writing a first NdArray program

The README points to a dedicated installation page in the repository, at docs/markdown/Installation.md, and a separate building page at docs/markdown/Building.md. Because the library is header-only, the practical shape of installation is putting the include directory on your compiler's include path; the repository keeps headers under include/ and ships a CMakeLists.txt plus a cmake/ directory. The README states the supported standards as C++17, C++20 and C++23, and lists Boost 1.73+ as a requirement, so a Boost installation is part of the setup. The repository also carries examples/, including an examples/ReadMe/ directory whose contents mirror the quick start guide. The README gives this example of constructing and reshaping an NdArray:

```cpp
nc::NdArray<int> a = { {1, 2}, {3, 4}, {5, 6} }
a.reshape(2, 3)
a.astype<double>()
```

The README also gives the initialiser forms nc::linspace<dtype>(1, 10, 5), nc::arange<dtype>(3, 7), nc::eye<dtype>(4), nc::zeros<dtype>(3, 4) and nc::ones<dtype>(3, 4), which return NdArrays for common needs. Compile with your C++17-or-later compiler, adding the NumCpp include directory and your Boost include path, for example with GNU 13.3 or later as listed in the README's tested compilers. A 1xN result is what you should expect from a linspace, since the README defines 1D arrays as 1xN. If the compiler cannot find the NumCpp headers, the include path is wrong; if it fails inside Boost headers, the Boost version is below 1.73.

## Random, concatenation and the parts that do not map one to one

The random module is a good illustration of how NumCpp tracks NumPy without copying it. NumPy's np.random.randn(3, 4) becomes nc::random::randN<double>(nc::Shape(3, 4)), or the brace form nc::random::randN<double>({3, 4}); np.random.randint(0, 10, [3, 4]) becomes nc::random::randInt<int>(nc::Shape(3, 4), 0, 10). The template argument is explicit in C++ where NumPy infers a dtype, and the shape is a first-class object. Concatenation follows the same pattern: np.vstack([a, b, c]) maps to nc::vstack({a, b, c}), np.hstack to nc::hstack, and np.stack([a, b, c], axis=0) to nc::stack({a, b, c}, nc::Axis::ROW). These are close enough that a port is mostly mechanical, but the differences are systematic rather than accidental. Where NumPy infers, NumCpp asks you to state. Where NumPy overloads an operator, NumCpp sometimes provides a named method. Read the translation table as the contract, and check the full documentation for anything the table omits, because the README calls the guide a very brief overview.

## Where NumCpp is the wrong tool

The clearest failure mode is expecting Python. NumCpp is a C++ library; it does not run Python code, and the README's translation table is a mapping of concepts, not an interpreter. If your pipeline depends on a Python package that wraps NumPy, or on SciPy, pandas or a plotting library, NumCpp cannot stand in. The second limitation is the 2D core. Since NdArray is inherently two-dimensional and 1D arrays are 1xN, code that assumes true rank-1 or rank-N semantics will need reshaping, and the DataCube container is described by the README itself as having limited usefulness beyond storing 2D arrays. The third is dependency surface: Boost 1.73+ is required, which is a real constraint in environments that pin an older Boost or forbid external dependencies. Finally, header-only means compile time. Every translation unit that includes the NumCpp headers instantiates templates, and the repository's own static_analysis/ and suppressions.txt entries suggest the maintainers manage that cost. For a small script, that is a bad trade. For a shipped binary, it usually is not.

## NumCpp compared with Xtensor, Pybind11 and staying in NumPy

The two alternatives people actually weigh against NumCpp are Xtensor and Pybind11, and they differ in approach rather than degree. Xtensor is a C++ array library built around expression templates and lazy evaluation, so an expression composes into a single evaluation rather than a chain of temporaries; NumCpp instead mirrors NumPy's eager model, which is why its API reads like a direct translation. If your priority is squeezing allocations out of a chain of arithmetic, that difference matters. Pybind11 takes the opposite route entirely: it keeps NumPy as the array type and exposes C++ functions to Python, so the numerical logic stays in Python-land and only the hot path is compiled. NumCpp has no Python binding layer in what the repository documents; it is the choice when the final artifact must be C++ with no interpreter present. There is also the option of doing nothing and staying in NumPy, which is correct whenever the Python runtime is acceptable. NumCpp only earns its place when the deployment target rules Python out.

## Maintenance, licence and upgrade cost

The repository is not archived, and its last push was on 2026-09-22, so there is current activity on the default branch. Recent releases are Version_2.16.1 on 2026-03-17, Version_2.16.0 on 2025-11-30 and Version_2.15.0 on 2025-11-27, which shows a release cadence of a few months rather than continuous tagging. The project is MIT licensed, which in practice means you can use it in closed-source products provided you keep the copyright and permission notice; that is a summary of the licence identifier, not legal advice, and your own counsel should confirm obligations for your distribution model. Upgrade cost is dominated by two things. First, the C++ standard floor: the README tests C++17, C++20 and C++23, so a project still on C++14 cannot adopt it without a language-version bump. Second, Boost 1.73+ is a hard requirement, and moving Boost forward can be a larger change than moving NumCpp forward. Because the library is header-only, there is no ABI to reconcile at link time, which removes the usual upgrade hazard but shifts the cost into recompilation.

## Conclusion

Adopt NumCpp if your numerical code must ship as a compiled C++ binary and you want NdArray, slicing and broadcasting to look like NumPy. Do not adopt it if you need real NumPy, SciPy or the Python package ecosystem, or if you cannot accept a Boost 1.73+ dependency and C++17 as the floor. Before committing, verify two things yourself: that your compiler appears in the tested list (Visual Studio 2022, GNU 13.3/14.2/15.2, Clang 18/19/20), and that the specific NumPy function you rely on exists in the library, because the README presents the quick start guide as a brief overview rather than a complete inventory.

## FAQ

### How do I install NumCpp?

The README links to an installation page at docs/markdown/Installation.md and a building page at docs/markdown/Building.md. Because NumCpp is header-only, the core step is making its include directory visible to your compiler, with Boost 1.73+ also required.

### What C++ standard does NumCpp require?

The README lists C++17, C++20 and C++23 as the tested standards, with Visual Studio 2022, GNU 13.3/14.2/15.2 and Clang 18/19/20 as the tested compilers. A project on C++14 would need a language-version bump first.

### Is NumCpp a drop-in replacement for NumPy?

No. NumCpp is a C++ library that mirrors NumPy's array model, and the README presents a translation table from NumPy calls to NumCpp calls. It does not run Python code or provide the wider Python package ecosystem.

### Does NumCpp support more than two dimensions?

The README states that NdArray is inherently a 2D array class with 1D arrays implemented as 1xN. A DataCube class exists as a container for an array of 2D NdArrays, but the README describes it as having limited usefulness past a simple container.

## Sources

- [dpilger26/NumCpp on GitHub](https://github.com/dpilger26/NumCpp)
- [License: MIT](https://github.com/dpilger26/NumCpp/blob/master/LICENSE)
- [Project website](https://dpilger26.github.io/NumCpp)
- [README](https://github.com/dpilger26/NumCpp/blob/master/README.md)
- [Releases](https://github.com/dpilger26/NumCpp/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dpilger26-numcpp
