ADR-0727: C++23 Wave 2 — project-wide cpp_std=c++23 bump and dict.c → dict.cpp¶
- Status: Accepted
- Date: 2026-05-28
- Deciders: lusoris
- Tags:
build,c++,cpp23,refactor,internals,fork-local,vmafx-rebrand
Context¶
ADR-0708 established the C++ migration playbook and converted metadata_handler.c to C++20 using an isolated-static-library pattern with override_options: ['cpp_std=c++20']. Wave 1 PRs (#41 mem, #43 opt,
44 fex_ctx_vector, #45 log v2) followed the same pattern at C++23, each¶
compiling in a per-file isolated static library rather than changing the project-wide default.
ADR-0708 flagged dict.c as the highest-ROI Wave 2 candidate because it has multiple out-param+return-code functions that map cleanly onto std::expected<T, int>, and because the project-wide cpp_std was still c++11 — too old for std::expected — making a project-wide bump a prerequisite.
The toolchain is already on gcc 14+ (post-ADR-0692) and clang 22, both well above the gcc >= 13 / clang >= 16 floor required for std::expected (C++23).
Decision¶
We will:
- Bump
core/meson.builddefault_optionsfromcpp_std=c++11tocpp_std=c++23project-wide. Wave 1override_options: ['cpp_std=c++23']on the isolated libs become redundant (but harmless); cleanup is deferred. - Convert
core/src/dict.ctocore/src/dict.cppusing real C++23 idioms:std::expected<T,int>for fallible internal helpers,std::string_viewfor read-only string params,std::unique_ptrfor RAII scratch buffers,[[nodiscard]]on every non-void return. Public C ABI is preserved exactly viaextern "C". - Rename
core/test/test_dict.ctocore/test/test_dict.cppto match and update the source reference incore/test/meson.build; the white-box#include "dict.cpp"probe for the staticisnumeric()helper is retained.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
Continue per-target override_options (Wave 1 pattern) | No project-wide change risk | Every new C++ file needs its own isolated static lib + override; boilerplate compounds per Wave | Ergonomics cost grows; project-wide bump is the documented end-state per ADR-0708 |
Stay at c++20 instead of c++23 | Slightly wider compiler compatibility floor | std::expected is not in C++20 | std::expected is the highest-ROI idiom for dict.cpp; C++23 floor is justified |
std::optional<T> + errno return instead of std::expected<T,int> | Lower compiler floor | Error path requires out-param again; loses monadic chaining | std::expected directly expresses "value or error code" — the existing semantic |
Keep const char * for internal params | No extra type | No bounds checking; no clear ownership signal | std::string_view for inputs, std::unique_ptr for owned buffers; const char * at the extern "C" boundary only |
Consequences¶
- Positive:
dict.cppinternal helpers use typed error propagation instead ofgotocleanup patterns; future C++ files incore/src/do not need isolated-static-lib boilerplate to use C++23 features. - Negative: Build toolchain floor rises from "any C++11 compiler" to gcc >= 13 / clang >= 16. The CI matrix is already gcc 14+/clang 22; no CI impact. External builds on old distros (Ubuntu 20.04 LTS, gcc 9) must upgrade or use the container image (
dev/Containerfile). - Neutral / follow-ups:
- Wave 1 isolated-static-lib
override_optionsare now redundant; cleanup deferred to Wave 3. test_dict.cppuses#include "dict.cpp"to white-box test the staticisnumeric()helper; this is a deliberate test-only pattern documented incore/AGENTS.md.