ADR-0809: C++23 Wave 8 — CLI conversion (cli_parse.c → .cpp, vmaf.c → .cpp)¶
- Status: Accepted
- Date: 2026-05-29
- Deciders: lusoris
- Tags:
cpp23,build,cli,raii,fork-local
Context¶
The CLI entry points core/tools/cli_parse.c and core/tools/vmaf.c were the last major .c files in the core/tools/ subtree. Several patterns in these files were suboptimal for C++:
NULLliterals instead ofnullptr— allows implicit integer-to-pointer conversion that C++ catches at compile time.- C-style casts (
(unsigned),(int),(uint16_t *)) — bypass type-safety checks. - String-option dispatch via repeated
strcmpchains — obscures intent and prevents compiler dead-code analysis;std::string_viewcomparisons are idiomatic and zero-cost. _Noreturn(C11 attribute) onusage()— C++ uses[[noreturn]].- The three parallel model-tracking arrays in
main()(model,model_collection,model_collection_label) were heap-allocated and freed manually in the goto-cleanup block. A leak was possible if future editing added an early-return path before the cleanup block. RAII eliminates this class of defect. spinner.hdefinedconst char *spinner[]with external linkage — safe while only included in one TU, but non-idiomatic;staticgives internal linkage in both C and C++.
The goto-cleanup spine in main() is a load-bearing invariant per ADR-0141 §2: each cleanup primitive (fclose / video_input_close / vmaf_close / vmaf_*_state_free / cli_free) must run in reverse-init order on every exit path, and splitting them into separate RAII helpers would require threading cleanup-relevant pointers through helper signatures in ways that obscure the unwind chain. The ModelArrays RAII struct replaces only the three parallel arrays whose cleanup is structurally identical (destroy + free); the spine itself is retained.
Decision¶
We will convert cli_parse.c → cli_parse.cpp and vmaf.c → vmaf.cpp with the following conservative C++23 idioms:
nullptrreplaces allNULLliterals.static_cast<T>()replaces all C-style casts.[[nodiscard]]on all helpers that return error-coded values.[[noreturn]]replaces_Noreturnonusage().std::string_viewfor option-string comparisons inresolve_precision_fmt,parse_pix_fmt,parse_aom_ctc,parse_nflx_ctc, the--backenddispatch, the--tiny-device/--dnn-epvalidation, the--tiny-resizevalidation, andresolve_tiny_deviceinvmaf.cpp.ModelArraysRAII struct invmaf.cppowns the three model-tracking arrays; its destructor callsvmaf_model_destroy/vmaf_model_collection_destroyand frees the backing store, replacing three manual free/destroy loops in the cleanup block.cli_parse.hgainsextern "C" { ... }guards so C callers continue to compile and link without modification.spinner.hgainsstaticonspinner[]andspinner_lengthfor internal linkage.meson.buildupdated: source list uses.cppfilenames;cpp_argsmirrorsc_argsfor the#ifdef HAVE_*defines;override_options : ['cpp_std=c++23']pins the standard per the pattern established in ADR-0708.
No functional changes. The vmaf --help output and the Netflix golden-pair score (76.66783, places=4 ≥ 76.668) are byte-for-byte identical to the pre-conversion binary.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
| Full RAII refactor of goto-cleanup spine | Eliminates all manual cleanup | Requires threading cleanup-relevant pointers through helper signatures; obscures unwind chain | Rejected — load-bearing invariant per ADR-0141 §2 |
Use std::string instead of std::string_view | More familiar | Allocates heap memory for transient comparisons | std::string_view is zero-cost and correct here |
Bump project cpp_std globally to C++23 now | Simpler build config | Potentially affects SYCL/CUDA TUs not yet tested at C++23 | Per-target override is safe; global bump deferred per ADR-0708 policy |
Keep .c and use C23 features only | No C++ compiler needed | C23 has no std::string_view, no RAII, no [[nodiscard]] with the same semantics | Cannot address the type-safety and RAII goals with C alone |
Consequences¶
- Positive:
ModelArraysdestructor guarantees model/collection teardown on all exit paths, including future ones.[[nodiscard]]on error-returning helpers makes it a compile-time error to silently discard a return value.std::string_viewcomparisons in option dispatch are self-documenting and remove allstrcmpchains in the option-validation paths. - Negative: Two more C++ TUs in the tools subtree; requires a C++ compiler on the build host (already required by
svm.cpp, all SYCL TUs, andmetadata_handler.cpp). - Neutral / follow-ups: The original
.cfiles are retained alongside the.cppfiles until the PR merges so bisect can diff them; they will be deleted in the merge commit.
References¶
- ADR-0708 — C++23 internals pilot;
extern "C"guard recipe. - ADR-0141 — touched-file lint-clean rule; goto-cleanup load-bearing invariant.
- ADR-0278 — NOLINT citation policy.
- req: "Convert
core/tools/cli_parse.candcore/tools/vmaf.cto .cpp. CLI parsing benefits from std::expected/std::string_view; CLI main benefits from RAII. Add extern \"C\" guards in cli_parse.h. Conservative C++23 idioms only (nullptr, static_cast, [[nodiscard]])."