build!: raise the standard to C++23, and spend it - #826
Merged
Conversation
andiwand
force-pushed
the
feat/cpp23
branch
2 times, most recently
from
September 6, 2026 09:22
a93fab2 to
aee7ad5
Compare
This was referenced Sep 6, 2026
The whole matrix already supports it: clang 18, gcc 14, apple-clang, MSVC 19.40, NDK 28.1 and emsdk 3.1.73. It costs no source change at all — the two transitive includes libc++'s C++23 headers stopped handing out went in with #827, which is the whole of it. Three ceilings sit under the standard, and AGENTS.md records all three because none is discoverable from a local build. The library half is capped by emsdk 3.1.73's libc++ 18.1, the oldest here and the newest emsdk conan-center packages. `std::format` is unusable on any slice — the apple profiles deploy to macOS 12 / iOS 15, and libc++ marks the floating-point `to_chars` that `<format>` instantiates as macOS 13.3 / iOS 16.3, which is why #828 formats through `fmt`. And NDK 28.1's clang 19 segfaults on a capturing recursive lambda taking `this auto self`. The public headers stay C++20 — no `target_compile_features(odr PUBLIC …)` and no `cppstd` in `package_info`, so a consumer picks its own standard, and `check_min_cppstd` sits in `validate_build` where it constrains building `odr` rather than using it. On MSVC there is no `/std:c++23`; CMake maps `CXX_STANDARD 23` to `/std:c++latest`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoTh7BEUSL2z9BBThgsEi7
Forty-two accessors were the same body twice, once for each constness, with only the spelled-out return type telling the two apart. An explicit object parameter deduces that, so each pair is one function returning `T &` or `const T &` from a single body. The shared registry (#823) is where it pays most: `SideTable` and `SortedSideTable` had written the workaround out by hand — two public overloads delegating to a `static` helper templated on the object, under a comment naming the trick — and every engine then repeated the pair for each of its payload accessors. Both go, and a registry's accessor is [[nodiscard]] auto &text_element_at(this auto &self, const ElementIdentifier id) { return self.m_texts.at(id); } `pdf`'s `Array` and `Dictionary` lose eleven more. `ooxml_text_list`'s numbering walk would have dropped its Y-combinator with them, but NDK 28.1's clang 19 segfaults on a capturing lambda that recurses through an explicit object parameter, so it keeps passing itself along and says why. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoTh7BEUSL2z9BBThgsEi7
`resize` fills the new tail with zeros; every one of these then writes over all of it. `resize_and_overwrite` hands the chunk over unwritten instead — 12.3 GB/s to 16.7 GB/s on `read_u8s` against an in-memory stream, which is what a zip entry is. The callback must not throw, so a short read shrinks the string back to the offset it started from and the throw happens at the call site. `ppt`'s `read_raw_text_bytes` loses its second `resize` with it: the callback returns `gcount()` and the string is already the right length. `xls_io`'s string body is left alone — its `read_bytes` throws from inside, and a non-throwing path just for this is not worth the buffer it saves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoTh7BEUSL2z9BBThgsEi7
Both loops did nothing but map a range onto a vector, and both fed it straight into one call. `views::transform | ranges::to<std::vector<std::string>>()` says that in the expression that uses it, so neither needs a named variable any more. `type1_charstring` takes the iterator-pair `std::reverse` next to it with them, per the ranges convention. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoTh7BEUSL2z9BBThgsEi7
The bump made `odr` C++23 and deliberately did not pass that on — no `target_compile_features(odr PUBLIC …)`, no `cppstd` in the conan `package_info` — so a consumer keeps whatever standard it picked, as long as `src/odr/*.hpp` stays C++20. Nothing checked that, and a `this auto &self` in a public header would have broken someone else's build rather than ours. `odr_public_headers_cpp20` includes all seventeen of them and compiles at C++20. One object file, no test data, no link, no gtest — it either compiles or it does not. It lives in `test/CMakeLists.txt` and so builds under `ODR_TEST`, which is also what keeps it out of the conan package: `exports_sources` ships no `test/`, and an unconditional target naming a file that is not there fails the package build at generate time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoTh7BEUSL2z9BBThgsEi7
The C++23-does-not-propagate rationale now lives in AGENTS.md alone; the test file and its cmake target state what they are and stop there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012QiYawTVZyuMLQBQngdTeu
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Generated with Claude Code
Raises the standard to C++23 and spends it on the three things this codebase
had waiting for it. Five commits, each reviewable on its own.
Rebased on
main, now that #827 (the parts that needed no C++23) and #828 (thelocale fix, which brought
fmtin) have landed. Everything left here genuinelyrequires the standard.
The bump costs nothing
The bump commit is build configuration and documentation only — no source change
at all. The two transitive includes libc++'s C++23 headers stopped handing out
went in with #827.
odr+odr_test+translate, RelWithDebInfo-Werror, asbuild_test.ymlruns itodr_testclang-tidyon every changed filemain, checked file by fileodrbuilt atCMAKE_OSX_DEPLOYMENT_TARGET=12.0fmtdoes not switch tostd::to_charsat C++23, 0 undefined symbolsconan install -s compiler.cppstd=23MSVC is the caveat: it has no
/std:c++23, and CMake mapsCXX_STANDARD 23to
/std:c++latest. Thewindows-2022-msvc-1940job moves onto C++26-draft mode.Three ceilings sit under the standard
None is visible from a local build, so AGENTS.md records all three.
emsdk 3.1.73's libc++ 18.1 — the oldest in theprofile matrix, and the newest emsdk conan-center packages, so not a pin that
can be bumped. No
views::enumerate,std::generator,std::move_only_function,std::flat_map.std::mdspanis out from theother side, since libstdc++ has none.
std::formatis unusable, on every slice. The apple profiles deploy tomacOS 12 / iOS 15 (
apple.jinja), and libc++ annotates the floating-pointstd::to_charsthat<format>formats through as introduced in macOS 13.3.<format>instantiates that formatter whatever the argument types are, soeven
std::format("{}:{}", host, port)fails — and one call anywhere insrc/breaks the framework build. A host-default compile passes and provesnothing;
-mmacosx-version-min=12.0is the check.thisis fine on accessors, not on a capturing recursive lambda.NDK 28.1's clang 19 segfaults on
[&](this auto self, …)that recurses. Onlycodegen shows it —
-fsyntax-onlypasses.CI found 2 and 3 on the first push of this branch, after a local run that had
tested with
-fsyntax-onlyand no deployment target. Both are now in AGENTS.mdwith the command that reproduces them.
What it is spent on
Deducing
this— 42 accessor pairs. #823 had just centralised the elementregistry, and
SideTable/SortedSideTablewrote the const workaround out byhand: two public overloads delegating to a
statichelper templated on theobject, under a comment naming the trick. Every engine then repeated the pair
for each payload accessor. Both go, and a registry accessor is now
pdf'sArrayandDictionarylose eleven more. −238/+118 lines.ooxml_text_list's Y-combinator stays, for ceiling 3 above, and says so.resize_and_overwrite— 12.3 → 16.7 GB/s onbyte_stream::read_u8s, thechunked read every binary engine goes through, measured against an in-memory
stream (which is what a zip entry is).
resizezero-fills a tail the very nextread overwrites. The callback must not throw, so a short read shrinks the string
back and the throw happens at the call site.
xls_io's string body is leftalone — its
read_bytesthrows from inside, and a non-throwing path just for itis not worth the buffer it saves.
ranges::tofor the two loops that did nothing but map a range into a vectorfed straight into one call.
The public headers stay C++20
Nothing propagates the standard — no
target_compile_features(odr PUBLIC …), nocppstdinpackage_info— so a consumer keeps whatever standard it picked, aslong as
src/odr/*.hppstays C++20. Nothing checked that, and athis auto &selfin a public header would have broken someone else's build rather than ours, so
the last commit adds
odr_public_headers_cpp20: all seventeen public headers inone TU at
cxx_std_20. One object file, no test data, no link, builds bydefault. It was verified to fail on a deducing-
thisconstruct and pass againwithout it.
conanfile.py'scheck_min_cppstdmoves to 23 but sits invalidate_build,which constrains building
odrrather than using it. That is the oneconsumer-visible change, and it has a CHANGELOG entry.
Not done, on purpose
std::format— ceiling 2, and off the table until the apple deploymenttargets reach macOS 13.3 / iOS 16.3. fix(html): keep the host's locale out of the css we write #828 already took the locale-safety it
would have bought, through
fmt. String building stays+/std::to_string;fmtis no faster at those lengths.std::unreachable— the switch defaultsthrowby design (AGENTS.md Failfast); swapping a throw for UB is backwards.
std::to_underlying— there is nostatic_cast<…>(std::underlying_type_t<…>)anywhere.
std::byteswap—util::byte::{from,to}_{little,big}_endianis alreadybyte-wise and portable, which is what keeps the big-endian gap tractable.
operator[]—Sheet::cell(column, row)would read assheet[column, row], but it is public API and a C++23-only spelling would pushthe standard onto consumers.