Skip to content

Add insert() to VariableLengthArray (#62) - #246

Merged
thirtytwobits merged 2 commits into
mainfrom
claude/issue-62-vla-insert
Oct 19, 2026
Merged

thirtytwobits merged 2 commits into
mainfrom
claude/issue-62-vla-insert

Conversation

@thirtytwobits

Copy link
Copy Markdown
Member

Adds std::vector::insert to cetl::VariableLengthArray. All five overloads are included, for both the general and the packed-bool versions:

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 Add reverse iterators to VariableLengthArray (#65); move CI to toolshed ts26.4.5 #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 VLA push_back/emplace_back store a wrong value when the argument is an element of the array and storage must grow #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

Add std::vector's five insert overloads to both the general and the
packed-bool specializations: one copy, one moved value, count copies, an
iterator range, and an initializer list. Each returns an iterator to
the first inserted element, or to pos if nothing was inserted.

New elements are constructed at the end of the array and std::rotate
moves them into place. That needs only what the standard requires of
insert (MoveAssignable and Swappable elements), and a range's elements
need only be constructible from *first, not assignable from it.
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:
- If constructing a new element, or reading or advancing a single-pass
  source, throws, the new elements are destroyed and the array is
  unchanged except, possibly, capacity (as for resize()).
- If moving existing elements throws during the rotation, every element
  stays alive and counted, in unspecified order.
- insert(pos, value) and insert(pos, count, value) accept a value that
  is an element of the array, even when inserting reallocates: the
  value is copied first when it aliases (std::less address check).
- With exceptions disabled, a sized insertion that exceeds max_size() or
  that the allocator declines has no effect. A single-pass range keeps
  the prefix that fit, 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: a new test_variable_length_array_insert suite (int and bool,
checked against std::vector) covers every position of several array
sizes across packed-byte boundaries with each overload and several
counts, mutable iterators, empty insertions on a never-allocated
array, geometric growth, max_size() with and without exceptions,
overload resolution, an aliased value with and without reallocation,
move-only and construct-only elements, no allocation within capacity,
declined growth, and narrowing conversions under -Wconversion. The
exception-safety suite covers construction, single-pass read and
advance failures, rotation failures, allocation failure and bool
conversion failures. Death tests cover both new assertions.

The fuzzer's emplace_back operation now also chooses among the insert
overloads from its spare opcode bits (input format unchanged) and
checks each returned position. Its std::vector model now rebuilds
itself instead of calling std::vector::insert, because libc++ 23's
vector<bool>::insert hits undefined behaviour (a shift by the full word
width in __copy_backward_unaligned) that UBSan reports, independent of
CETL.

Closes #62.

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-19T22:31:52.438497Z 460719d 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.

The insert tests and the fuzzer computed `x.insert(...) - x.begin()`
and `x.insert(...) == x.begin() + n` in one expression. The order in
which an expression's operands are evaluated is unspecified, and
insert() may reallocate. GCC 15's 32-bit C++17 build evaluated
std::vector's begin() first, then subtracted iterators into two
different buffers, so verification-amd64 (gcc-native-32, cpp-17)
failed AllOverloadsMatchVector against a garbage oracle. The same
mistake was fixed in the erase tests in #242.

Keep each returned iterator in a variable and compare it in a separate
statement, in the tests and in the fuzzer. No library change.

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. Already looking forward to the next diff.

Reviewed commit: 460719d3ba

ℹ️ 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 8e29f14 into main Oct 19, 2026
30 checks passed
@thirtytwobits
thirtytwobits deleted the claude/issue-62-vla-insert branch October 19, 2026 23:21
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 insert() to VLA

1 participant