From ea96083fd5c718445ef1bf4b0b06a64f69ff74f2 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 24 Aug 2026 18:52:25 -0700 Subject: [PATCH 1/3] Core: Adds missing operator[] to ArrayIteratorBase --- src/axom/core/ArrayIteratorBase.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/axom/core/ArrayIteratorBase.hpp b/src/axom/core/ArrayIteratorBase.hpp index 73991b6ab5..2e52242797 100644 --- a/src/axom/core/ArrayIteratorBase.hpp +++ b/src/axom/core/ArrayIteratorBase.hpp @@ -6,7 +6,7 @@ #pragma once -#include "axom/core/IteratorBase.hpp" // for Iterator +#include "axom/core/IteratorBase.hpp" namespace axom { @@ -45,14 +45,16 @@ class ArrayIteratorBase : public IteratorBaseflatIndex(BaseType::m_pos); } + /// \brief Returns the value at offset \a n from the current iterator position + AXOM_HOST_DEVICE + ValueType& operator[](IndexType n) const { return m_arrayPtr->flatIndex(BaseType::m_pos + n); } + protected: - /** Implementation of advance() as required by IteratorBase */ + /// Implementation of advance() as required by IteratorBase AXOM_HOST_DEVICE void advance(IndexType n) { BaseType::m_pos += n; } From 91db18945304393d3236ac34dc098851d2ec79e9 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 24 Aug 2026 19:09:45 -0700 Subject: [PATCH 2/3] Core: Adds unit/regression tests for ArrayIteratorBase::operator[] --- src/axom/core/tests/CMakeLists.txt | 1 + src/axom/core/tests/core_array_iterator.hpp | 159 ++++++++++++++++++++ src/axom/core/tests/core_serial_main.cpp | 1 + 3 files changed, 161 insertions(+) create mode 100644 src/axom/core/tests/core_array_iterator.hpp diff --git a/src/axom/core/tests/CMakeLists.txt b/src/axom/core/tests/CMakeLists.txt index c2e1e2b209..8627630aac 100644 --- a/src/axom/core/tests/CMakeLists.txt +++ b/src/axom/core/tests/CMakeLists.txt @@ -18,6 +18,7 @@ set(core_serial_tests core_about.hpp core_array.hpp + core_array_iterator.hpp core_array_mapping.hpp core_array_for_all.hpp core_utilities.hpp diff --git a/src/axom/core/tests/core_array_iterator.hpp b/src/axom/core/tests/core_array_iterator.hpp new file mode 100644 index 0000000000..d9d871664c --- /dev/null +++ b/src/axom/core/tests/core_array_iterator.hpp @@ -0,0 +1,159 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + +#include "gtest/gtest.h" + +#include "axom/core/Array.hpp" +#include "axom/core/ArrayView.hpp" +#include "axom/core/ItemCollection.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +// Detects whether subscripting a const iterator is a valid expression. +template +struct has_subscript : std::false_type +{ }; + +template +struct has_subscript()[std::declval< + typename std::iterator_traits::difference_type>()])>> : std::true_type +{ }; +} // namespace + +//------------------------------------------------------------------------------ +TEST(core_array_iterator, random_access_contract_is_static) +{ + using ArrayIter = axom::Array::ArrayIterator; + using ConstArrayIter = axom::Array::ConstArrayIterator; + using ViewIter = decltype(std::declval&>().begin()); + + static_assert(std::is_same::iterator_category, + std::random_access_iterator_tag>::value, + "Array's iterator advertises random access"); + static_assert(std::is_same::iterator_category, + std::random_access_iterator_tag>::value, + "Array's const iterator advertises random access"); + static_assert(std::is_same::iterator_category, + std::random_access_iterator_tag>::value, + "ArrayView's iterator advertises random access"); + + static_assert(has_subscript::value, "Array iterator needs i[n]"); + static_assert(has_subscript::value, "Array const_iterator needs i[n]"); + static_assert(has_subscript::value, "ArrayView iterator needs i[n]"); + + static_assert(std::is_same()[0]), double&>::value, + "Array iterator subscripting must preserve mutability"); + static_assert(std::is_same()[0]), const double&>::value, + "Array const_iterator subscripting must return a const reference"); + + // Note: The subscript is defined on ArrayIteratorBase, not on the common IteratorBase + // used by derived forward-only iterators (like ItemCollection) + using ForwardIter = axom::ItemCollection::iterator; + static_assert(!has_subscript::value, + "ItemCollection's forward iterator must not acquire random access"); + + SUCCEED(); +} + +//------------------------------------------------------------------------------ +TEST(core_array_iterator, subscript_matches_offset_dereference) +{ + axom::Array arr(5); + std::iota(arr.begin(), arr.end(), 10); + + auto it = arr.begin(); + for(axom::IndexType n = 0; n < arr.size(); ++n) + { + EXPECT_EQ(it[n], *(it + n)); + EXPECT_EQ(it[n], arr[n]); + } + + // Subscript is relative to the iterator's position, not to begin(). + auto mid = arr.begin() + 2; + EXPECT_EQ(mid[0], arr[2]); + EXPECT_EQ(mid[2], arr[4]); + EXPECT_EQ(mid[-2], arr[0]); +} + +//------------------------------------------------------------------------------ +TEST(core_array_iterator, subscript_is_a_mutable_reference) +{ + axom::Array arr(3); + arr.fill(0); + + const auto it = arr.begin(); + it[1] = 42; + EXPECT_EQ(arr[1], 42); +} + +//------------------------------------------------------------------------------ +// Regression: libc++ 22 rewrote std::__sift_down to index the iterator rather +// than dereference it, so std::sort over an axom::Array failed to compile. +// See numerics::solve_polynomial_durand_kerner, which sorts an +// axom::Array>. +//------------------------------------------------------------------------------ +TEST(core_array_iterator, sort_over_array_of_complex) +{ + using Complex = std::complex; + + axom::Array roots; + roots.push_back(Complex {3.0, 0.0}); + roots.push_back(Complex {1.0, 2.0}); + roots.push_back(Complex {1.0, -2.0}); + roots.push_back(Complex {2.0, 0.0}); + + std::sort(roots.begin(), roots.end(), [](const Complex& lhs, const Complex& rhs) { + if(lhs.real() != rhs.real()) + { + return lhs.real() < rhs.real(); + } + return lhs.imag() < rhs.imag(); + }); + + EXPECT_EQ(roots[0], Complex(1.0, -2.0)); + EXPECT_EQ(roots[1], Complex(1.0, 2.0)); + EXPECT_EQ(roots[2], Complex(2.0, 0.0)); + EXPECT_EQ(roots[3], Complex(3.0, 0.0)); +} + +//------------------------------------------------------------------------------ +TEST(core_array_iterator, heap_algorithms_over_array_view) +{ + axom::Array arr(6); + const int values[6] = {5, 1, 4, 2, 6, 3}; + for(axom::IndexType i = 0; i < arr.size(); ++i) + { + arr[i] = values[i]; + } + + axom::ArrayView view(arr); + + // make_heap/sort_heap route through __sift_down, which requires the subscript operator. + std::make_heap(view.begin(), view.end()); + EXPECT_TRUE(std::is_heap(view.begin(), view.end())); + + std::sort_heap(view.begin(), view.end()); + EXPECT_TRUE(std::is_sorted(view.begin(), view.end())); + EXPECT_EQ(view[0], 1); + EXPECT_EQ(view[5], 6); + + // partial_sort also reaches __sift_down. + std::partial_sort(view.begin(), view.begin() + 3, view.end(), std::greater {}); + EXPECT_EQ(view[0], 6); + EXPECT_EQ(view[1], 5); + EXPECT_EQ(view[2], 4); +} diff --git a/src/axom/core/tests/core_serial_main.cpp b/src/axom/core/tests/core_serial_main.cpp index cd134f8696..9945393157 100644 --- a/src/axom/core/tests/core_serial_main.cpp +++ b/src/axom/core/tests/core_serial_main.cpp @@ -20,6 +20,7 @@ #include "core_execution_for_all.hpp" #include "core_execution_scans.hpp" #include "core_execution_space.hpp" +#include "core_array_iterator.hpp" #include "core_map.hpp" #include "core_flatmap.hpp" #include "core_flatmap_for_all.hpp" From 07d1dbc7859692e2750a838e2b5eb6bee5a07052 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 24 Aug 2026 21:04:50 -0700 Subject: [PATCH 3/3] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index cf273a76c4..f8e6ecb9d9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -117,6 +117,7 @@ and marked the templated `axom::sidre::View::getAttributeScalar()` overloads so they can be called on a `const View`. Also added `const` overloads for `axom::sidre::Buffer::getData()` and `axom::sidre::Buffer::getVoidPtr()` so they can be called on a `const Buffer`. - Quest: Fixes `InOutOctree::within()` for query points that lie on (or very near) the surface, in both 2D (segment meshes) and 3D (triangle meshes). +- Core: Adds missing subscript operator to ArrayIteratorBase to satisfy random access contract. ## [Version 0.14.0] - Release date 2026-03-31