Stratax 0.3.1
Loading...
Searching...
No Matches
Slice Ops

Slice Operations

Version: v0.2.0

Status: Complete

Header: include/stratax/indexing/Slicing.hpp


Overview

The slicing API copies strided selections from Vector, Matrix, and Tensor containers into new owning containers. It resolves raw stratax::core::Slice bounds against concrete dimension extents, supports positive and negative steps, clamps bounds, and preserves traversal order.

const stratax::Vector<int> source{0, 1, 2, 3, 4, 5};
const auto result = stratax::indexing::slice(
source,
// result contains {1, 3, 5}.
Describes a signed, strided half-open index range.
Definition Slice.hpp:31

Responsibilities

The slicing module is responsible for:

  • Resolving signed raw bounds against each source dimension
  • Translating negative bounds relative to the dimension extent
  • Clamping bounds for positive and negative traversal
  • Computing output shapes
  • Copying selected values in row-major result order
  • Checking Tensor result-offset arithmetic

It does not provide non-owning views, omitted-bound syntax, broadcasting, or advanced gather indexing.


Type Aliases

using size_type = std::size_t;
using difference_type = std::ptrdiff_t;

size_type represents extents, output counts, and flat offsets. difference_type represents normalized signed positions and steps.


Slice Normalization

The internal detail::normalize_slice helper converts one raw Slice into:

struct ResolvedSlice
{
difference_type start;
difference_type step;
size_type size;
};

Normalization is O(1):

  • An extent larger than difference_type::max() is rejected.
  • Negative bounds are translated relative to the extent.
  • Positive-step bounds are clamped to [0, extent].
  • Negative-step bounds are clamped to [-1, extent - 1].
  • For a negative step, a stop value of -1 remains the reverse-range sentinel instead of being translated.
  • Directionally empty ranges resolve to size zero.

For example, Slice{-1, -1, -1} selects a complete dimension in reverse order.


Vector Slicing

template<typename T>
const stratax::core::Slice& slice);
One-dimensional owning array of numeric values.
Definition Vector.hpp:44

The slice is resolved against vec.size(). Selected values are copied into a new rank-one Vector in traversal order.

const Vector<int> values{0, 1, 2, 3, 4};
auto odds = slice(values, Slice{1, 5, 2}); // {1, 3}
auto reversed = slice(values, Slice{-1, -1, -1}); // {4, 3, 2, 1, 0}

Throws:

  • Exceptions::IndexError("Vector slice out of bounds.") if the source extent cannot be represented by difference_type
  • std::bad_alloc if result allocation fails

Complexity: O(k), where k is the result size.


Matrix Slicing

template<typename T>
const stratax::core::Slice& rows,
const stratax::core::Slice& cols);
Two-dimensional owning array of numeric values.
Definition Matrix.hpp:46

Row and column ranges are resolved independently. The returned Matrix has shape {resolved_rows.size, resolved_cols.size} and owns a row-major copy of the selected rectangle.

const Matrix<int> matrix{
{ 0, 1, 2, 3},
{ 4, 5, 6, 7},
{ 8, 9, 10, 11}
};
auto result = slice(matrix, Slice{0, 3, 2}, Slice{1, 4, 2});
// Shape{2, 2}, values {1, 3, 9, 11}.

Throws:

  • Exceptions::IndexError("Matrix row slice out of bounds.") if the row extent cannot be represented
  • Exceptions::IndexError("Matrix column slice out of bounds.") if the column extent cannot be represented
  • std::bad_alloc if result allocation fails

Complexity: O(result.rows() * result.cols()).


Variadic Tensor Slicing

template<typename T, typename... Slices>
Slices... slices);
Arbitrary-rank owning array of numeric values.
Definition Tensor.hpp:50

Every variadic argument must be exactly core::Slice, enforced with a compile-time assertion. Exactly one Slice must be supplied per tensor dimension.

Each dimension is normalized independently. Its selected count becomes the corresponding output dimension, and selected values are copied into the result in row-major order.

Tensor<int> tensor(Shape{2, 3, 4});
auto result = slice(
tensor,
Slice{0, 2},
Slice{0, 3, 2},
Slice{1, 4, 2});
// result.shape() == Shape{2, 2, 2}

Throws:

  • Exceptions::IndexError("Tensor slice rank must match tensor rank.") if the number of Slice arguments differs from tensor.rank()
  • Exceptions::IndexError("Tensor slice out of bounds.") if a dimension extent cannot be represented by difference_type
  • Exceptions::DimensionError("Tensor slice offset overflow.") if checked source-offset arithmetic overflows
  • Exceptions::DimensionError if output shape or stride arithmetic overflows
  • Any allocation or element-copy exception propagated while constructing the owning result

Complexity: O(result.size() * tensor.rank()).

An empty selected dimension returns an empty Tensor with the fully resolved shape. A rank-zero Tensor can be sliced by supplying no Slice arguments.


Vector-based Tensor Slicing

template<typename T>
const std::vector<stratax::core::Slice>& slices);

This overload performs the same normalization and copy operation but accepts a runtime-sized vector of Slice objects.

const std::vector<Slice> ranges{
Slice{0, 2},
Slice{0, 3, 2},
Slice{1, 4, 2}
};
auto result = slice(tensor, ranges);

Throws:

Complexity: O(result.size() * tensor.rank()).

For equivalent ranges, this overload produces the same shape and values as the variadic overload.


Ownership and Empty Results

Every overload returns an independent owning container. Modifying a result does not modify the source, and result storage does not alias source storage.

Out-of-range bounds are normally clamped rather than rejected. Directionally empty ranges and zero-sized source dimensions produce correctly shaped empty containers without entering the element-copy loops.


Complexity Summary

Operation Complexity
Normalize one Slice O(1)
Vector slicing O(result.size())
Matrix slicing O(result.rows() * result.cols())
Tensor slicing O(result.size() * tensor.rank())

All overloads allocate storage proportional to the number of selected elements, plus output shape/stride metadata.


Design Notes

Slicing currently materializes owning copies. This keeps lifetime and mutation semantics straightforward but makes even contiguous selections O(n).

Both Tensor overloads throw RankError when the number of slices does not match the tensor rank.


Future Improvements

  • Deduplicate the two Tensor copy implementations
  • Make normalization arithmetic safe across the full signed range
  • Support omitted bounds and full-range shorthand
  • Add non-owning strided views

See Also