Skip to content

initialize: correct the class range's iterator category, drop a homonym - #107

Open
jll63 wants to merge 2 commits into
boostorg:developfrom
jll63:fix/initialize-context-tidy
Open

initialize: correct the class range's iterator category, drop a homonym#107
jll63 wants to merge 2 commits into
boostorg:developfrom
jll63:fix/initialize-context-tidy

Conversation

@jll63

@jll63 jll63 commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

(Written by Claude Code, on behalf of @jll63.)

Two small corrections to the API a policy's initialize sees. Neither changes behaviour;
both remove a statement that is not true.

1. The class range is an input range, not a forward one

const_class_iterator declared forward_iterator_tag while its reference is
class_view — a prvalue built on the fly by operator*. A forward iterator owes a real
reference and a multipass guarantee, and this one can give neither: two iterators at the same
position hand out views at different addresses. libstdc++ says so outright under
_GLIBCXX_CONCEPT_CHECKS, which rejects any algorithm asserting _ForwardIteratorConcept
over the range:

error: static assertion failed: reference type of a forward iterator must be a real reference

Nothing in the tree acted on the promise — fast_perfect_hash, vptr_vector and vptr_map
only ever ++, != and dereference, and std::distance needs no more — but
classes_begin()/classes_end() are public API, so an out-of-tree policy author is entitled
to. The tag becomes input_iterator_tag, and the blueprint says why.

Not a regression. Before the class range was reshaped, the iterator advertised the same
tag with reference spelled const class_info*& while operator* returned a prvalue — so
even std::iterator_traits<It>::reference r = *it; failed to compile. This is a
long-standing mis-tag, not something a recent change broke.

type_id_begin()/type_id_end() still say forward in the blueprint, correctly: those
return a pointer.

Worth knowing for context: under C++20, std::forward_iterator is satisfied by both the
old and the new iterator, so std::ranges accepts the range either way. Only the C++17
taxonomy — and libstdc++'s concept checks, which implement it — is affected.

2. class_info::vptr() was a misleading homonym

It returned *static_vptr, the v-table pointer from the previous initialize(), by
reference. class_view::vptr(), which is what a policy sees, returns the one being staged,
by value. The two differ for every class on every pass — and differ precisely while the
policies run, which is when a reader of initialize.hpp is most likely to reach for the
wrong one.

One caller was left, a trace line that took its address, so &cr.vptr() was a two-step
spelling of cr.static_vptr: correct, but roundabout. That becomes cr.static_vptr and the
accessor goes. No behaviour change — the address of *static_vptr is static_vptr. Nothing
outside include/ ever used it, and class_info is never handed to a policy (the
InitializeContext blueprint exposes only class_view), so the confusion was confined to
future edits inside the library.

Test

test/test_initialize_context.cpp is new — nothing in the suite looked at the class range's
category, or at the range at all from a policy's point of view. It rides a policy along on a
normal registry to get a Context, then pins the category and the reason for it (that
reference and value_type are the same type), counts the classes through
std::distance, and checks that single-pass does not mean unstable: two iterators at the
same position describe the same class.

Confirmed it catches a regression: with the tag put back to forward_iterator_tag, it fails
on the first static_assert.

Verification

  • CMake/Ninja, gcc, Debug: 161/161 ctest pass.
  • b2 toolset=gcc: no failures.
  • clang-format-22 applied.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JQa4fuiwcfsheZYTCyfPPr

jll63 and others added 2 commits September 12, 2026 11:00
`const_class_iterator` declared `forward_iterator_tag` while its `reference`
is `class_view` - a prvalue built on the fly by `operator*`. A forward iterator
owes a real reference and a multipass guarantee; this one can give neither:
two iterators at the same position hand out views at different addresses.
libstdc++ says so outright under `_GLIBCXX_CONCEPT_CHECKS`, which rejects an
algorithm asserting `_ForwardIteratorConcept` over the range with "reference
type of a forward iterator must be a real reference".

Nothing in the tree acted on the promise - `fast_perfect_hash`, `vptr_vector`
and `vptr_map` only ever `++`, `!=` and dereference, and `std::distance` needs
no more - but `classes_begin()`/`classes_end()` are public API, so an
out-of-tree policy author is entitled to. Say input, and say in the blueprint
why.

Not a regression: before the class range was reshaped, the iterator advertised
the same tag with `reference` spelled `const class_info*&` while `operator*`
returned a prvalue, so even `std::iterator_traits<It>::reference r = *it;` did
not compile. This is a long-standing mis-tag, now corrected.

The `type_id_begin()`/`type_id_end()` blueprint still says forward, correctly:
those return a pointer.

test_initialize_context.cpp pins the category, and the reason for it - that
`reference` and `value_type` are the same type. It fails the first assertion
if the tag goes back to forward.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQa4fuiwcfsheZYTCyfPPr
`detail::class_info::vptr()` returned `*static_vptr` - the v-table pointer
from the *previous* initialize(), by reference. `class_view::vptr()`, which is
what a policy sees, returns the one being *staged*, by value. The two differ
for every class on every pass, and differ precisely while the policies run,
which is when a reader of initialize.hpp is most likely to reach for the wrong
one.

One caller was left, a trace line that took its address - so `&cr.vptr()` was
a two-step spelling of `cr.static_vptr`, correct but roundabout. Say
`cr.static_vptr` and delete the accessor.

No behaviour change: the address of `*static_vptr` is `static_vptr`. Nothing
outside include/ ever used it, and `class_info` is never handed to a policy -
the InitializeContext blueprint exposes only `class_view` - so the confusion
was confined to future edits inside the library.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQa4fuiwcfsheZYTCyfPPr
@cppalliance-bot

Copy link
Copy Markdown

An automated preview of the documentation is available at https://107.openmethod.prtest3.cppalliance.org/libs/openmethod/doc/html/index.html

If more commits are pushed to the pull request, the docs will rebuild at the same URL.

2026-09-12 15:07:04 UTC

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.

2 participants