Skip to content

univector: one-dimensional containers

univector is KFR's one-dimensional container and a 1D input/output expression. It is suitable for sample buffers, coefficient lists, and the result of a finite expression. Its storage form is selected by the second template argument.

Owning vectors

univector<T> is the usual dynamically sized, owning form. It uses KFR's aligned allocator, so its storage is suitable for SIMD processing. Its size is known at run time:

#include <kfr/base.hpp>

using namespace kfr;

univector<float> samples(1024);
samples = truncate(counter<float>(), samples.size());

univector<T, N> owns exactly N elements and stores them inline, like an aligned std::array. Use it for small buffers and fixed algorithm state:

univector<float, 4> coefficients{ 0.25f, 0.5f, 0.25f, 0.0f };
univector<float, 4> squares = sqr(counter<float>());

The dynamic form derives from std::vector<T, data_allocator<T>>, and the fixed-size form derives from std::array<T, N>. All members provided by those corresponding standard containers are therefore also available on univector.

Both forms accept 0D or 1D expressions. A fixed vector evaluates into its existing N elements. Constructing a dynamic vector from an expression requires a finite source and resizes it to the source extent:

univector<int> values = truncate(counter(), 10);

univector<int> another;
another = truncate(counter(), 10); // resizes to 10

Assignment from an infinite 1D expression does not resize a dynamic vector; it uses the vector's current length as the bound. This is useful when filling a preallocated processing buffer:

univector<float> block(256);
block = counter<float>();

Non-owning views

univector_ref is an alias for a non-owning univector view. It contains a pointer and length, does not allocate or copy, and can use a const-qualified element type for a read-only view. Create it with make_univector:

float raw[] = { 10, 20, 30 };
univector_ref<float> view = make_univector(raw);

view[1] = 99; // raw is now { 10, 99, 30 }

const std::vector<float> input = { 1, 2, 3 };
auto read_only = make_univector(input); // univector_ref<const float>

array_ref is the more general pointer-and-size range used by KFR. It has ordinary range access (data, size, iterators, operator[]) but is not itself an expression container. It is similar to C++20's std::span. univector_ref derives from array_ref and adds the 1D expression interface.

Neither view owns its elements or extends their lifetime. Do not keep a view of a temporary container, an initializer list, or storage that may be reallocated.

Slicing and references

.slice and .truncate member functions on an expression-capable univector return non-owning views of its storage. They clamp the requested range rather than extending past the end:

univector<float> samples = { 0, 1, 2, 3, 4, 5 };

auto middle = samples.slice(2, 3); // aliases { 2, 3, 4 }
middle = -middle;                  // samples becomes { 0, 1, -2, -3, -4, 5 }

auto first = samples.truncate(2);  // aliases { 0, 1 }
auto all   = samples.ref();        // array_ref<float> over all elements

A const vector returns univector_ref<const T> from slice and truncate.

There is also a free slice for lazy expression slicing. It works for expressions of every rank. The member form above is the simpler choice when an already-materialized vector needs a direct storage view.

Ring-buffer helpers

An owning or mutable reference univector has small circular-buffer helpers. They use a cursor supplied by the caller and wrap at the vector boundary:

univector<float, 4> delayline{};
size_t cursor = 3;

delayline.ringbuf_write(cursor, 1.0f); // stores at index 3; cursor becomes 0

delayline.ringbuf_write(cursor, 2.0f); // stores at index 0; cursor becomes 1

float value;
delayline.ringbuf_read(cursor, value); // reads index 1 and advances

Keep the cursor in [0, size()) and use a nonempty buffer. These helpers are for local circular storage; they do not provide synchronization.

For a thread-shared single-producer/single-consumer queue, see spsc_ring_buffer in Small containers and an SPSC queue.

Choosing a form

Requirement Recommended type
Size is chosen at run time and the vector owns data univector<T>
Size is part of the algorithm's compile-time contract univector<T, N>
Existing contiguous storage should be processed without a copy univector_ref<T> / make_univector
A generic, non-expression pointer-and-size range is enough array_ref<T>

Use render or direct expression construction when a finite 1D expression should become an owning vector. Use a reference view when an algorithm should operate on caller-owned data in place.