C++23 extractor conversion pattern¶
When converting a core/src/*.c internal file to C++23 per the ADR-0708 / ADR-0723 migration plan, follow this checklist.
Why convert?¶
Internal implementation files have no ABI constraint — they are never included by downstream consumers. Converting them to C++23 enables:
- RAII lifecycle management (
std::unique_ptr, destructors) replacing manualmalloc/freepairs. std::vector<T*>replacing manual realloc/grow patterns.std::spanfor read-only views over raw pointer+length pairs.[[nodiscard]]on return-by-value factory functions.nullptr,constexpr,static_assertthroughout.
Step-by-step¶
1. Rename the file¶
2. Add extern "C" guards to the public header¶
The header is included by C callers; wrap the entire public surface:
#ifdef __cplusplus
extern "C" {
#endif
/* existing struct typedefs and function declarations */
#ifdef __cplusplus
} /* extern "C" */
#endif
3. Handle feature_extractor.h in the .cpp file¶
feature_extractor.h uses atomic_int (via <stdatomic.h> in C, or <atomic> + using std::atomic_int in C++). Including <atomic> inside an extern "C" block causes a hard error ("template with C linkage"). The required include order:
/* 1. Pull in <atomic> BEFORE any extern "C" block. */
#include <atomic>
/* 2. Wrap all internal C headers that lack extern "C" guards. */
extern "C" {
#include "feature/feature_extractor.h"
#include "feature/feature_name.h"
#include "log.h"
/* ... other internal C headers ... */
} /* extern "C" */
/* 3. Include your own header last (it already has extern "C" guards). */
#include "foo.h"
The <atomic> include guard short-circuits the second pull from inside feature_extractor.h, so no duplication occurs.
4. Update core/src/meson.build¶
Mirror the metadata_handler_cpp20_lib pattern:
foo_cpp23_lib = static_library(
'foo_cpp23',
src_dir + 'foo.cpp',
include_directories : [vmaf_base_include, libvmaf_include],
pic : true,
install : false,
)
Do not add override_options : ['cpp_std=...']. The C++ standard is project-wide: core/meson.build probes the newest -std=c++2x the compiler's standard library actually supports (or /std:c++latest on MSVC) and injects it through add_project_arguments (ADR-1003 / ADR-1056). Meson emits that flag after any per-target cpp_std= option, so a per-target override never changed the effective standard; the redundant ones were removed under epic #1241 (see core/AGENTS.md, invariant 7).
Remove the .c entry from libvmaf_sources and add a comment pointing to the new lib. Add foo_cpp23_lib.extract_all_objects(recursive: true) to the objects: list of the final library() target (alongside metadata_handler_cpp20_lib and log_cpp23_lib).
The isolated library is the pattern most conversions use. The simpler alternative is to list src_dir + 'foo.cpp' directly in libvmaf_sources, which is what fex_ctx_vector.cpp and thread_locale.cpp do.
5. Update core/test/meson.build¶
Any test executable that directly compiles ../src/foo.c must be updated to ../src/foo.cpp. Meson handles mixed C/C++ executable() targets transparently — metadata_handler.cpp and fex_ctx_vector.cpp are both already compiled this way in test_feature_extractor (core/test/meson.build).
6. Exception policy¶
All std::bad_alloc (or any other exception) must be caught before crossing the extern "C" boundary and converted to an int error code (typically -ENOMEM). The public C API signatures are unchanged.
Reference implementations¶
| File | ADR | C++ idioms used |
|---|---|---|
core/src/metadata_handler.cpp | ADR-0708 | std::unique_ptr + custom deleter for linked-list teardown |
core/src/fex_ctx_vector.cpp | ADR-0723 | C-compatible pointer-array ownership with checked malloc/realloc; extern "C" + pre-<atomic> include pattern |