Skip to content

Commit cca2b94

Browse files
committed
Simplify view conversions, view ops
* ra/arrays.hh (ViewBig, ViewSmall): Replace const/unconst conversions with conversions from Slice. (reconst, unconst): Unused, remove. (reshape, colapse, stencil): Take Slice arguments.
1 parent 3238743 commit cca2b94

File tree

8 files changed

+303
-186
lines changed

8 files changed

+303
-186
lines changed

README.md

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
**ra-ra** implements [expression templates](https://en.wikipedia.org/wiki/Expression_templates). This is a C++ technique (pioneered by [Blitz++](http://blitz.sourceforge.net)) to delay the execution of expressions involving array operands, and in this way avoid the unnecessary creation of large temporary array objects.
99

10-
**ra-ra** is compact (≈4k loc), easy to extend, and generic. There are no arbitrary type restrictions or limits on rank or argument count.
10+
**ra-ra** is compact, generic, and easy to extend.
1111

12-
In this example ([examples/read-me.cc](examples/read-me.cc)), we create some arrays, do operations on them, and print the result.
12+
In this example ([examples/read-me.cc](examples/read-me.cc)), we create some arrays, operate on them, and print the result.
1313

1414
```c++
1515
#include "ra/ra.hh"
@@ -38,7 +38,7 @@ B:
3838
{215.00, 218.00, -221.00, -224.00}}
3939
```
4040
41-
Please check the manual online at [lloda.github.io/ra-ra](https://lloda.github.io/ra-ra), or have a look at the [examples/](examples/) folder.
41+
Check the manual at [lloda.github.io/ra-ra](https://lloda.github.io/ra-ra), or have a look at the [examples/](examples/).
4242
4343
**ra-ra** offers:
4444
@@ -50,48 +50,45 @@ Please check the manual online at [lloda.github.io/ra-ra](https://lloda.github.i
5050
* iterators over subarrays (cells) of any rank
5151
* rank conjunction as in J (compile time rank only), outer product operation
5252
* short-circuiting logical operators
53-
* operators to select from argument list using either bool or integer (`where`, `pick`)
53+
* operators to select from argument list (`where`, `pick`)
5454
* view operations: reshape, transpose, reverse, collapse/explode, stencils
5555
* arbitrary types as array elements, or as scalar operands
56-
* many predefined array operations, adding yours is trivial
56+
* many predefined array operations; adding yours is trivial
5757
* configurable error handling
5858
* as much `constexpr` as possible.
5959
60-
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/](bench/).
60+
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. Have a look at the benchmarks in [bench/](bench/).
6161
6262
#### Building the tests and the benchmarks
6363
64-
**ra-ra** is header-only and has no dependencies other than a C++23 compiler and the standard library. At the moment I test with gcc 14.2. If you can test with Clang, please let me know.
64+
**ra-ra** is header-only and only depends on the standard library. A C++23 compiler is required. At the moment I test with gcc 14. If you can test with Clang, please let me know.
6565
66-
The test suite in [test/](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/). Please check the manual for options.
66+
The test suite in [test/](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/). Check the manual for options.
6767
6868
#### Notes
6969
7070
* Both index and size types are signed. Index base is 0.
71-
* The default array order is C or row-major (last dimension changes fastest). You can make array views with other orders, but newly created arrays use C order.
71+
* The default array order is C or row-major (last dimension changes fastest). You can make array views with other orders.
7272
* The subscripting operator is `()` or `[]`, indistinctly.
73-
* Indices are checked by default. This can be disabled with a compilation flag.
73+
* Indices are checked by default. Checks can be disabled with a compilation flag.
7474
7575
#### Bugs & defects
7676
7777
* Operations that require allocation, such as concatenation or search, are mostly absent.
7878
* No good abstraction for reductions. You can write reductions abusing rank extension, but it's awkward.
79-
* Traversal of arrays is basic, just unrolling of inner dimensions.
79+
* Basic array traversal, just unrolling of inner dimensions.
8080
* Handling of nested / ragged arrays is inconsistent.
81-
* No support for parallel operations or GPU.
82-
* No SIMD to speak of.
83-
84-
Please see the TODO file for a concrete list of known issues.
81+
* No parallel operations, GPU, or SIMD (but should be thread safe).
8582
8683
#### Motivation
8784
88-
I do numerical work in C++, and I need support for array operations. The built-in array types that C++ inherits from C are very insufficient, so at the time of C++11 when I started writing **ra-ra**, a number of libraries were already available. However, most of these libraries seemed to support only vectors and matrices, or small objects for vector algebra.
85+
I do numerical work in C++, and I need support for array operations. The built-in array types that C++ inherits from C are insufficient, so at the time of C++11 when I started writing **ra-ra**, a number of libraries were already available. However, most of these libraries seemed to support only vectors and matrices, or small objects for vector algebra.
8986
9087
Blitz++ was a major inspiration as an early *generic* library. But it was a heroic feat to write such a library in C++ in the late 90s. Variadic templates, lambdas, perfect forwarding, etc. make things much easier, for the library writer as well as for the user.
9188
92-
From APL and J I've taken the rank extension mechanism, and perhaps an inclination for carrying each feature to its logical end.
89+
From APL and J I've taken the rank extension mechanism, as well as an inclination for carrying each feature to its logical end.
9390
94-
**ra-ra** wants to remain simple. 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 implementating new traversal methods, or perhaps optimizing specific expression patterns, should be possible without having to turn the library inside out.
91+
I want **ra-ra** to remain simple. I try not to second-guess the compiler and I don't fret over performance as much as Blitz++ did. However, I'll avoid features that could become an obstacle if I ever tried to make things fast(er). It should be possible to implement new traversal methods, or perhaps optimize specific expression patterns, without having to turn the library inside out.
9592
9693
#### Other C++ array libraries
9794

box/view.cc

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// -*- mode: c++; coding: utf-8 -*-
2+
// ra-ra/box - Refactor Views (WIP)
3+
4+
// (c) Daniel Llorens - 2026
5+
// This library is free software; you can redistribute it and/or modify it under
6+
// the terms of the GNU Lesser General Public License as published by the Free
7+
// Software Foundation; either version 3 of the License, or (at your option) any
8+
// later version.
9+
10+
#include <iostream>
11+
#include <iterator>
12+
#include "ra/test.hh"
13+
14+
using std::cout, std::endl, std::flush;
15+
16+
// TODO
17+
// fix deduction of f(view); once viewbig is an alias R isn't deduced anymore. Handle viewsmall & viewbig together.
18+
19+
namespace ra {
20+
21+
template <class Dimv>
22+
struct ViewBase
23+
{
24+
constexpr static Dimv const simv = {};
25+
[[no_unique_address]] Dimv dimv; // FIXME const after init
26+
};
27+
28+
template <is_ctype Dimv>
29+
struct ViewBase<Dimv>
30+
{
31+
constexpr static auto simv = Dimv::value;
32+
constexpr static auto dimv = simv;
33+
};
34+
35+
template <class P, class Dimv>
36+
struct View: public ViewBase<Dimv>
37+
{
38+
static_assert(has_len<P> || std::bidirectional_iterator<P>);
39+
P cp;
40+
using ViewBase<Dimv>::simv, ViewBase<Dimv>::dimv;
41+
constexpr static bool CT = is_ctype<Dimv>;
42+
// exclude T and sub constructors by making T & sub noarg
43+
constexpr static bool have_braces = CT && std::is_reference_v<decltype(*cp)>;
44+
using T = std::conditional_t<have_braces, std::remove_reference_t<decltype(*cp)>, noarg<>>;
45+
// ---------
46+
// on dimv
47+
constexpr static rank_t R = rank_frame(size_s<decltype(simv)>(), 0);
48+
consteval static rank_t rank() requires (ANY!=R) { return R; }
49+
constexpr rank_t rank() const requires (ANY==R) { return rank_t(dimv.size()); }
50+
#define RAC(k, f) is_ctype<decltype(simv[k].f)>
51+
constexpr static dim_t len_s(auto k) { if constexpr (CT || RAC(k, len)) return len(k); else return ANY; }
52+
constexpr static dim_t len(auto k) requires (CT || RAC(k, len)) { return simv[k].len; }
53+
constexpr dim_t len(int k) const requires (!(CT || RAC(k, len))) { return dimv[k].len; }
54+
constexpr static dim_t step(auto k) requires (CT || RAC(k, step)) { return k<rank() ? simv[k].step : 0; }
55+
constexpr dim_t step(int k) const requires (!(CT || RAC(k, step))) { return k<rank() ? dimv[k].step : 0; }
56+
#undef RAC
57+
// ---------
58+
// maybe remove entirely
59+
constexpr dim_t size() const requires (!CT) { return ra::size(*this); }
60+
constexpr bool empty() const requires (!CT) { return any(0==map(&Dim::len, dimv)); }
61+
consteval static dim_t size() requires (CT) { return std::apply([](auto ... i){ return (i.len * ... * 1); }, dimv); }
62+
consteval static dim_t slen() { if constexpr (CT) return size(); else return ANY; } // FIXME maybe do without
63+
consteval bool empty() const requires (CT) { return any(0==map(&Dim::len, dimv)); }
64+
// ---------
65+
// common constructors
66+
constexpr explicit View(P cp_): cp(cp_) {} // empty dimv, but also uninit by slicers, esp. has_len<P>
67+
constexpr View(View const & s) = default;
68+
// ---------
69+
// constructors for CT
70+
template <class PP> constexpr View(View<PP, Dimv> const & x) requires (CT): View(x.cp) {} // FIXME Slice
71+
constexpr View const &
72+
operator=(T (&&x)[have_braces ? slen() : 0]) const
73+
requires (CT && have_braces && R>1 && slen()>1)
74+
{
75+
std::ranges::copy(std::ranges::subrange(x), begin()); return *this;
76+
}
77+
constexpr View const &
78+
operator=(typename nested_arg<T, Dimv>::sub (&&x)[have_braces ? (R>0 ? len_s(0) : 0) : 0]) const
79+
requires (CT && have_braces && 0<R && 0<len_s(0) && (1!=R || 1!=len_s(0)))
80+
{
81+
ra::iter<-1>(*this) = x;
82+
if !consteval { asm volatile("" ::: "memory"); } // patch for [ra01]
83+
return *this;
84+
}
85+
// ---------
86+
// constructors for !CT
87+
constexpr View() requires (!CT) {} // used by Container constructors
88+
constexpr View(Slice auto const & x) requires (!CT): View(x.dimv, x.cp) {}
89+
constexpr View(auto const & s, P cp_) requires (!CT && requires { [](dim_t){}(VAL(s)); }): cp(cp_) { filldimv(ra::iter(s), dimv); }
90+
constexpr View(auto const & s, P cp_) requires (!CT && requires { [](Dim){}(VAL(s)); }): cp(cp_) { resize(dimv, ra::size(s)); ra::iter(dimv) = s; } // [ra37]
91+
template <class D> using dimbraces = std::conditional_t<R==ANY, std::initializer_list<D>, D (&&)[R==ANY?0:R]>;
92+
constexpr View(std::conditional_t<!CT, dimbraces<dim_t>, noarg<dim_t>> s, P cp_) requires (!CT): View(ra::iter(s), cp_) {}
93+
constexpr View(std::conditional_t<!CT, dimbraces<Dim>, noarg<Dim>> s, P cp_) requires (!CT): View(ra::iter(s), cp_) {}
94+
// ---------
95+
// row-major & nested braces for !CT
96+
constexpr View const & operator=(std::initializer_list<T> x) const requires (!CT && 1!=R)
97+
{
98+
RA_CK(size()==ssize(x), "Mismatched sizes ", size(), " ", ssize(x), ".");
99+
std::ranges::copy(std::ranges::subrange(x), begin()); return *this;
100+
}
101+
constexpr View const & operator=(braces<T, R> x) const requires (!CT && R!=ANY) { iter<-1>() = x; return *this; }
102+
#define RA_BRACES(N) \
103+
constexpr View const & operator=(braces<T, N> x) const requires (!CT && R==ANY) { iter<-1>() = x; return *this; }
104+
RA_FE(RA_BRACES, 2, 3, 4);
105+
#undef RA_BRACES
106+
// ---------
107+
// CT
108+
template <dim_t s, dim_t o=0> constexpr auto as() const requires (CT) { return from(*this, ra::iota(ic<s>, o)); }
109+
template <rank_t c=0> constexpr auto iter() const requires (CT) { return Cell<P, Dimv, ic_t<c>>(cp); }
110+
constexpr auto iter(rank_t c) const requires (CT) { return Cell<P, decltype(dimv) const &, dim_t>(cp, dimv, c); }
111+
constexpr auto begin() const requires (CT) { if constexpr (c_order(dimv)) return cp; else return STLIterator(iter()); }
112+
constexpr auto end() const requires (CT && c_order(dimv)) { return cp+size(); }
113+
constexpr static auto end() requires (CT && !c_order(dimv)) { return std::default_sentinel; }
114+
constexpr decltype(auto) back() const requires (CT) { static_assert(size()>0, "Bad back()."); return cp[size()-1]; }
115+
// ---------
116+
// !CT
117+
template <rank_t c=0> constexpr auto iter() const && requires (!CT) { return Cell<P, Dimv, ic_t<c>>(cp, std::move(dimv)); }
118+
template <rank_t c=0> constexpr auto iter() const & requires (!CT) { return Cell<P, Dimv const &, ic_t<c>>(cp, dimv); }
119+
constexpr auto iter(rank_t c) const && requires (!CT) { return Cell<P, Dimv, dim_t>(cp, std::move(dimv), c); }
120+
constexpr auto iter(rank_t c) const & requires (!CT) { return Cell<P, Dimv const &, dim_t>(cp, dimv, c); }
121+
constexpr auto begin() const requires (!CT) { return STLIterator(iter<0>()); }
122+
constexpr decltype(auto) static end() requires (!CT) { return std::default_sentinel; }
123+
constexpr decltype(auto) back() const requires (!CT) { dim_t s=size(); RA_CK(s>0, "Bad back()."); return cp[s-1]; }
124+
// conversion to const, used by Container::view(). FIXME cf CT cases
125+
constexpr operator View<T const *, Dimv> const & () const requires (!CT && !std::is_const_v<T>)
126+
{
127+
return *reinterpret_cast<View<T const *, Dimv> const *>(this);
128+
}
129+
// ---------
130+
// either
131+
// T not is_scalar [ra44]
132+
constexpr View const & operator=(T const & t) const { ra::iter(*this) = ra::scalar(t); return *this; }
133+
// cf RA_ASSIGNOPS_ITER [ra38][ra34]
134+
View const & operator=(View const & x) const { ra::iter(*this) = x; return *this; }
135+
#define RA_ASSIGNOPS(OP) \
136+
constexpr View const & operator OP(auto const & x) const { ra::iter(*this) OP x; return *this; } \
137+
constexpr View const & operator OP(Iterator auto && x) const { ra::iter(*this) OP RA_FW(x); return *this; }
138+
RA_FE(RA_ASSIGNOPS, =, *=, +=, -=, /=)
139+
#undef RA_ASSIGNOPS
140+
constexpr decltype(auto) operator()(this auto && self, auto && ... i) { return from(RA_FW(self), RA_FW(i) ...); }
141+
constexpr decltype(auto) operator[](this auto && self, auto && ... i) { return from(RA_FW(self), RA_FW(i) ...); }
142+
constexpr decltype(auto) at(auto const & i) const { return *indexer(*this, cp, ra::iter(i)); }
143+
constexpr operator decltype(*cp) () const { return to_scalar(*this); }
144+
constexpr auto data() const { return cp; }
145+
};
146+
147+
template <class P, rank_t R> using ViewB = View<P, std::conditional_t<ANY==R, vector_default_init<Dim>, std::array<Dim, ANY==R ? 0 : R>>>;
148+
template <class P, class Dimv> using ViewS = View<P, Dimv>;
149+
150+
} // namespace ra
151+
152+
int main()
153+
{
154+
ra::TestRecorder tr;
155+
156+
return tr.summary();
157+
}

0 commit comments

Comments
 (0)