Skip to content

Add reverse iterators to VariableLengthArray (#65); move CI to toolshed ts26.4.5 - #244

Merged
thirtytwobits merged 4 commits into
mainfrom
claude/issue-65-vla-reverse-iterators
Oct 19, 2026
Merged

thirtytwobits merged 4 commits into
mainfrom
claude/issue-65-vla-reverse-iterators

Conversation

@thirtytwobits

@thirtytwobits thirtytwobits commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Adds reverse iterators to cetl::VariableLengthArray, matching std::vector::rbegin and rend:

using reverse_iterator       = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;

reverse_iterator       rbegin() noexcept;    const_reverse_iterator rbegin() const noexcept;
reverse_iterator       rend() noexcept;      const_reverse_iterator rend() const noexcept;
const_reverse_iterator crbegin() const noexcept;
const_reverse_iterator crend() const noexcept;

Both the general and the packed-bool versions get them. For bool, std::reverse_iterator wraps the existing proxy iterators: writing through them works, and since #242 made iterator convert to const_iterator, a reverse_iterator converts to a const_reverse_iterator and the two compare and subtract in either order. Like the forward iterators, reverse iterators are invalidated by reserve() and shrink_to_fit(). An array that has never allocated has an empty reverse range.

Tests

New test_variable_length_array_reverse_iterators.cpp, typed over int and bool and checked against std::vector:

  • member types, return types of every accessor (mutable and const), and noexcept
  • reverse traversal at sizes 0, 1, 7, 8, 9 and 17 (packed-byte boundaries for bool), distance, base(), and random access
  • the empty range of a never-allocated array
  • writing through the adaptor (*rbegin() = …, std::fill) and std::reverse_copy as an oracle
  • mixed reverse_iterator and const_reverse_iterator: conversion, ==, !=, <, > and subtraction in both orders
  • searching from the back with std::find
  • std::string elements, including operator-> through the adaptor

I left out a "const_reverse_iterator doesn't convert to reverse_iterator" check. Before C++20, libstdc++'s std::reverse_iterator converting constructor is unconstrained (the constraint came with LWG 3435), so the trait reports true there even for std::vector. It would test the standard library rather than VLA.

Validation

  • GCC 13, C++14: all 15 VLA test suites pass in Debug and DebugEP (-fno-exceptions)
  • GCC 13, C++20, and Clang C++20 with ASan + UBSan: the new suite passes
  • GCC 13 and Clang, C++14/17/20/23, with and without exceptions: the new, compat and bool suites compile cleanly with -Wall -Wextra -Werror -Wconversion -Wsign-conversion -pedantic
  • toolshed ts24.4.3: the new suite compiles cleanly with GCC 7 (C++14), GCC 13 (C++20) and Clang 19 (C++20)
  • clang-format 19.1.7 from toolshed:ts24.4.3 reports no changes; git diff --check is clean

Bit proxy swap (from review)

VLA<bool>'s proxy reference now has a hidden-friend swap(reference, reference), and the class has the static swap(reference, reference) that std::vector<bool> provides. Without them, std::reverse, std::rotate, std::sort and other algorithms that swap through std::iter_swap did not compile on bits, with forward iterators as well as the new reverse ones.

CI: toolshed ts26.4.5

CI moves from toolshed:ts24.4.3 to ts26.4.5, the latest release (Ubuntu 26.04).

Job Image Matrix
cache-warmup ts26.4.5 —
verification-amd64 ts26.4.5 gcc-native-32, clang-native × C++14/17/20/23
verification-arm64 ts26.4.5 gcc-native, clang-native × C++14/17/20
verification-legacy-arm64 (new) ts24.4.3 gcc7-native × C++14
docs ts26.4.5 —

ts26.4.x no longer ships g++-7, so GCC 7 moves to its own job on the last image that has it. The new image upgrades GCC 13.3 to 15.2, Clang 19.1 to 22.1, clang-format 19.1 to 22.1, and CMake 3.31 to 4.2. That needed a few fixes, in a separate commit:

  • clang-format: every build runs the header format check. clang-format 22 lays out four declarations in optional.hpp, variant.hpp and rtti.hpp differently from 19, so they are wrapped in clang-format off/on. Every header now has zero violations under both 19 (the GCC 7 job) and 22.
  • type_traits_ext.hpp: detail::copy_list_initialize declared a by-value volatile parameter for volatile targets. C++20 deprecates that, and Clang 22 errors on it under -Werror, which would also hit users. It now takes std::remove_cv_t<To>.
  • example_07: dropped a virtual destructor in a final class (-Wunnecessary-virtual-specifier, new in Clang 22).
  • test_variable_length_array_bool.cpp: the std::vector<bool> oracle used an unsigned char allocator. The standard requires bool, and GCC 15 enforces it from C++20.

Checked in ts26.4.3, which has the same toolchains as ts26.4.5; later releases only added linters and Bazel. Results: the gcc-native and clang-native preset workflows at C++14/17/20/23 (232 test runs each), the docs build, and the online cache warmup. I couldn't run gcc-native-32 locally, so this PR's CI is its first run on the new image.

Closes #65.

🤖 Generated with Claude Code

Add the reverse_iterator and const_reverse_iterator member types
(std::reverse_iterator over iterator and const_iterator), and the
rbegin(), rend(), crbegin() and crend() accessors, with const overloads
of rbegin() and rend(), matching std::vector. This covers both ordinary
elements and packed bool. All six accessors are noexcept, and like the
forward iterators they are invalidated by reserve() and shrink_to_fit().
An array that has never allocated has an empty reverse range.

For packed bool, the reverse iterators wrap the proxy iterators.
Writing through them works, and because iterator converts to
const_iterator, a reverse_iterator converts to a const_reverse_iterator
and the two compare and subtract in either order.

A new test_variable_length_array_reverse_iterators suite (int and bool,
checked against std::vector) covers the member and return types,
noexcept, traversal at byte-boundary sizes, base(), random access, the
empty array, writing through the adaptor, mixed reverse iterators, a
search from the back, and std::string elements through operator->.

Closes #65.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thirtytwobits

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T20:41:05.675232Z a504f5c Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8176795137

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread include/cetl/variable_length_array.hpp
Dereferencing a VLA<bool> iterator gives a temporary reference proxy,
which std::swap cannot take. So algorithms that swap elements through
std::iter_swap, such as std::reverse, std::rotate and std::sort, did not
compile on bits, whether through the new reverse iterators or the
forward ones. std::vector<bool> avoids this with a swap overload for
its proxies.

Add a hidden-friend swap(reference, reference) on the proxy, which ADL
finds from std::iter_swap, and the static
VariableLengthArray<bool>::swap(reference, reference) that
std::vector<bool> also provides. Both exchange the two bits and are
noexcept.

A new test runs std::reverse over reverse and forward ranges,
std::rotate, std::iter_swap and std::sort on bits against
std::vector<bool>, and swaps proxies directly both ways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thirtytwobits

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: afcdd3b92b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

thirtytwobits and others added 2 commits October 19, 2026 12:55
Preparation for moving CI to toolshed ts26.4.5. Its compilers and
clang-format are newer than ts24.4.3's (GCC 15.2 for 13.3, Clang 22.1
for 19.1, clang-format 22.1 for 19.1, CMake 4.2 for 3.31), and every
build runs the header format check.

- clang-format 22 lays out four declarations differently from 19: three
  declarations whose constexpr sits behind a preprocessor guard, a
  defaulted template argument, and two enable_if return types. Wrap
  them in clang-format off/on, as the headers already do elsewhere, so
  both versions accept every header. The GCC 7 job stays on ts24.4.3
  and clang-format 19.
- type_traits_ext: detail::copy_list_initialize took a by-value To.
  For To = volatile int that is a volatile-qualified parameter, which
  C++20 deprecates, and Clang 22 reports it even in this unevaluated
  use (-Wdeprecated-volatile, an error under -Werror for anyone using
  is_convertible_without_narrowing with a volatile target). Take
  std::remove_cv_t<To> instead; top-level cv does not affect how the
  parameter is initialized.
- example_07: drop `virtual` from the destructor of a final class whose
  base destructor is not virtual (-Wunnecessary-virtual-specifier, new
  in Clang 22).
- test_variable_length_array_bool: the std::vector oracle used
  std::allocator<unsigned char> for std::vector<bool>. The standard
  requires the allocator's value_type to match, and GCC 15's libstdc++
  enforces it in C++20 and later. Use a bool allocator for the oracle;
  VLA<bool> keeps its unsigned char allocator.

Verified in ts26.4.3: the GCC and Clang preset workflows at C++14, 17,
20 and 23 (232 test runs each), the docs build and the online cache
warmup; zero clang-format 19 or 22 violations in include/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run cache-warmup, verification-amd64, verification-arm64 and docs in
ghcr.io/opencyphal/toolshed:ts26.4.5, the latest release (Ubuntu 26.04;
GCC 15.2, Clang 22.1, clang-format 22.1, CMake 4.2).

ts26.4.x no longer ships g++-7, which the gcc7-native toolchain pins.
GCC 7 moves out of the arm64 matrix into a new
verification-legacy-arm64 job that stays on ts24.4.3, the last image
with it, so pinned old compilers have a home separate from the
current-toolchain jobs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thirtytwobits thirtytwobits changed the title Add reverse iterators to VariableLengthArray (#65) Add reverse iterators to VariableLengthArray (#65); move CI to toolshed ts26.4.5 Oct 19, 2026
@thirtytwobits

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: a504f5cc0a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@thirtytwobits
thirtytwobits merged commit 27b6696 into main Oct 19, 2026
30 checks passed
@thirtytwobits
thirtytwobits deleted the claude/issue-65-vla-reverse-iterators branch October 19, 2026 21:07
thirtytwobits added a commit that referenced this pull request Oct 19, 2026
Adds
[`std::vector::insert`](https://en.cppreference.com/w/cpp/container/vector/insert)
to `cetl::VariableLengthArray`. All five overloads are included, for
both the general and the packed-`bool` versions:

```cpp
iterator insert(const_iterator pos, const T& value);
iterator insert(const_iterator pos, T&& value);
iterator insert(const_iterator pos, size_type count, const T& value);
template <class InputIt> iterator insert(const_iterator pos, InputIt first, InputIt last);
iterator insert(const_iterator pos, std::initializer_list<T> ilist);
```

These cover the range and initializer-list forms requested in the issue
comments. Each returns an iterator to the first inserted element, or to
`pos` if nothing was inserted.

## How it works
New elements are constructed at the end of the array, then `std::rotate`
moves them into place.
- This needs only what the standard requires of `insert`: elements must
be MoveAssignable and Swappable. Elements are only *constructed* from a
range's `*first`, never assigned from it, so a type that is
constructible but not assignable from the source works. (The `bool`
specialization rotates through the proxy `swap` added in #244.)
- Capacity grows geometrically, as for `push_back()`, so repeated
insertion stays amortized. Forward ranges are measured and reserved
once; single-pass ranges are appended one element at a time.

## Guarantees
| Situation | Result |
| --- | --- |
| Constructing a new element throws, or a single-pass source's read or
advance throws | The new elements are destroyed and the array is
unchanged, except that capacity may stay grown (as with `resize()`) |
| Moving existing elements throws during the rotation | Every element
stays alive and counted; their order is unspecified |
| `insert(pos, v[i])` or `insert(pos, n, v[i])`, even when inserting
reallocates | Correct: an aliased value is copied first |
| Exceptions disabled; a sized insertion exceeds `max_size()` or the
allocator declines | No effect; detect it with `size()` |
| Exceptions disabled; a single-pass range doesn't fit | The prefix that
fit is kept, as `assign(first, last)` does |

New debug assertions reject positions outside the array: `CDE_vla_013`,
and `CDE_vla_014` for `bool`, which also rejects another array's
iterator.

## Tests
- **New `test_variable_length_array_insert.cpp`**, typed over `int` and
`bool` and checked against `std::vector`:
- every position in arrays of 0, 1, 7, 8, 9 and 17 elements (crossing
packed-byte boundaries), for each overload and several counts, including
random-access, forward, single-pass and initializer-list sources
- mutable iterator positions, and empty insertions on a never-allocated
array
  - geometric growth, and `max_size()` with and without exceptions
  - overload resolution (`insert(pos, 3, 4)` and `insert(pos, {5})`)
  - an aliased value, with and without reallocation
- move-only elements, and elements that are constructible but not
assignable from the source
- no allocation when capacity suffices, declined growth, and narrowing
conversions under `-Wconversion -Werror`
- **Exception safety:**
- failures while constructing, reading or advancing leave the array
unchanged
  - rotation failures keep every element alive
  - allocation failure changes nothing
  - a `bool` conversion failure restores the bits
- **Death tests:** for both new assertions.
- **Fuzzer:** the `emplace_back` operation now also chooses among the
insert overloads from its spare opcode bits, so the input format is
unchanged, and checks each returned position.

## Found along the way
- **libc++ bug:** libc++ 23's own `std::vector<bool>::insert` has
undefined behaviour. It shifts a word by its full width in
`__copy_backward_unaligned`, which UBSan reports. I reproduced it with
plain `std::vector<bool>` and no CETL code. The fuzzer's reference model
therefore rebuilds itself instead of calling `std::vector::insert`.
- **CETL bug, filed separately as #245:** `push_back(v[0])` and
`emplace_back(v[0])` store the wrong value when they have to grow the
storage. `insert` doesn't have this problem.

## Validation
- GCC 13, C++14: all 16 VLA test suites pass in Debug and DebugEP
(`-fno-exceptions`)
- GCC 7 (toolshed `ts24.4.3`): the insert and exception-safety suites
pass with and without exceptions
- toolshed `ts26.4.3`: the insert and exception-safety suites compile
cleanly with GCC 15 (C++14, C++20) and Clang 22 (C++23, libc++)
- Clang, C++20, ASan + UBSan (with pointer-overflow checking): the
insert and exception-safety suites pass
- GCC 13 and Clang, C++14/17/20/23, with and without exceptions: the
insert, exception-safety, debug-assert and bool suites compile cleanly
with `-Wall -Wextra -Werror -Wconversion -Wsign-conversion -Wfloat-equal
-pedantic`
- libFuzzer, default and embedded profiles: ctest passes, plus 90 s runs
of about 850k–900k inputs each with no findings
- Header formatting: clang-format 19.1.7 (`ts24.4.3`) and 22.1.2
(`ts26.4.3`) both report no changes; `git diff --check` is clean

Closes #62.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add rbegin()/crbegin()/rend()/crend() to VLA

1 participant