A C++20 array and expression template library with some J/APL features
lloda e7caee49ae Make View's parameter a pointer and not a value type | 3 lat temu | |
---|---|---|
bench | 3 lat temu | |
box | 4 lat temu | |
config | 4 lat temu | |
docs | 3 lat temu | |
examples | 3 lat temu | |
ra | 3 lat temu | |
test | 3 lat temu | |
.gitattributes | 4 lat temu | |
.travis.yml | 4 lat temu | |
CMakeLists.txt | 5 lat temu | |
LICENSE | 9 lat temu | |
README.md | 3 lat temu | |
SConstruct | 5 lat temu | |
TODO | 3 lat temu |
ra-ra is a C++20, header-only multidimensional array library in the spirit of Blitz++.
Multidimensional arrays are containers that can be indexed in multiple dimensions. For example, vectors are arrays of rank 1 and matrices are arrays of rank 2. C has built-in multidimensional array types, but even in modern C++ there's very little you can do with those, and a separate library is required for any practical endeavor.
ra-ra implements expression templates. This is a C++ technique (pioneered by Blitz++) to delay the execution of expressions involving large array operands, and in this way avoid the unnecessary creation of large temporary array objects.
ra-ra tries to distinguish itself from established C++ libraries in this space (such as Eigen or Boost.MultiArray) by being more APLish, more general, smaller, and more hackable.
In this example (examples/readme.cc), we add each element of a vector to each row of a matrix, and then print the result.
#include "ra/ra.hh"
#include <iostream>
int main()
{
ra::Big<float, 2> A {{1, 2}, {3, 4}}; // compile-time rank, dynamic shape
A += std::vector<float> {10, 20}; // rank-extending op with STL object
std::cout << "A: " << A << std::endl; // shape is dynamic, so it will be printed
}
⇒
A: 2 2
11 12
23 24
Please check the manual online at lloda.github.io/ra-ra, or have a look at the examples/ folder.
ra-ra offers:
where
with bool selector, or pick
with integer selector).There is some constexpr
support for the compile time size types. For example, this works:
constexpr ra::Small<int, 3> a = { 1, 2, 3 };
using T = std::integral_constant<int, ra::sum(a)>;
static_assert(T::value==6);
Performance is competitive with hand written scalar (element by element) loops, but probably not with cache-tuned code such as your platform BLAS, or with code using SIMD. Please have a look at the benchmarks in bench/.
The library itself is header-only and has no dependencies other than a C++20 compiler and the standard library.
The test suite in test/ runs under either SCons (CXXFLAGS=-O3 scons
) or CMake (CXXFLAGS=-O3 cmake . && make && make test
). Running the test suite will also build and run the examples (examples/) and the benchmarks (bench/), although you can also build each of these separately. None of them has any dependencies, but some of the benchmarks will try to use BLAS if you have RA_USE_BLAS=1
in the environment.
The tests pass under gcc 10.2 (earlier versions don't support -std=c++20
or have bugs). Remember to pass -O2
or -O3
to the compiler, otherwise some of the tests will take a very long time to run. Clang 10 doesn't currently work (I'll keep trying) but the code is meant to be standard C++.
()
. []
means exactly the same as ()
. It's unfortunate that []
was wasted on subscripting when ()
works perfectly well for that...Please have a look at TODO for a concrete list of known bugs.
I do numerical work in C++, so I need a library of this kind. Most C++ array libraries seem to support only vectors and matrices, or small objects for low-dimensional vector algebra. Blitz++ was a great early generic array library (even though the focus was numerical) and it hasn't really been replaced as far as I can tell.
It was a heroic feat to write a library such as Blitz++ in C++ in the late 90s, even discounting the fragmented compiler landscape and the patchy support for the standard at that time. Variadic templates, lambdas, rvalue arguments, etc. make things much simpler, for the library writer as well as for the user.
From APL and J I've taken the rank extension mechanism, and perhaps an inclination for carrying each feature to its logical end.
ra-ra wants to remain a simple library. I try not to second-guess the compiler and I don't stress performance as much as Blitz++ did. However, I'm wary of adding features that could become an obstacle if I ever tried to make things fast(er). I believe that the implementation of new traversal methods, or perhaps the optimization of specific expression patterns, should be possible without having to turn the library inside out.