Case 169: Overload Added to a Previously Unique Function¶
| Field | Value |
|---|---|
| Verdict | ๐ก COMPATIBLE_WITH_RISK |
| Category | Risk |
| Platforms | Linux, macOS, Windows |
| Flags | โ |
Detected ChangeKinds |
overload_added |
| Source files | examples/case169_overload_added/ |
Category: Overload Resolution / Source Compatibility | Verdict: โ ๏ธ COMPATIBLE_WITH_RISK
Verdict and consumer impact¶
v2 adds a float overload next to the until-now unique
units::to_celsius(double). Nothing breaks at the binary level: the
double symbol (_ZN5units10to_celsiusEd) is byte-for-byte untouched and
old binaries keep working. The risk fires at the next recompile of
every consumer: a v1-era call like to_celsius(98.6f) promoted the float
to double and bound to the only overload; recompiled against v2 headers,
overload resolution now prefers the exact-match float overload โ a
different result, different precision, no warning, no diff at the call
site. Address-taking (&units::to_celsius) and template argument deduction
also break outright, with a compile error rather than silent drift.
Old/new diff¶
| v1.h | v2.h |
|---|---|
double to_celsius(double fahrenheit); |
double to_celsius(double fahrenheit); (unchanged) |
| (no other overload) | float to_celsius(float fahrenheit); (new) |
abicheck command¶
g++ -shared -fPIC -g v1.cpp -o libv1.so
g++ -shared -fPIC -g v2.cpp -o libv2.so
abicheck compare libv1.so libv2.so
Expected abicheck finding¶
Verdict: COMPATIBLE_WITH_RISK (exit 0)
Deployment Risk Changes:
- overload_added: Overload added to previously non-overloaded function:
units::to_celsius โ &units::to_celsius becomes ambiguous and overload
resolution may change
Additions:
- func_added: New public function: units::to_celsius(float)
Minimum evidence¶
min_evidence: L0 โ abicheck groups exported functions by their
scope-qualified name (parsed structurally from the Itanium mangling of each
.dynsym entry, e.g. _ZN5units10to_celsiusEd vs. _ZN5units10to_celsiusEf),
so overload-set growth is visible from the symbol table alone; no debug
info or headers needed.
Why abicheck catches it¶
abicheck reports overload_added (RISK) when a name that had exactly one
declaration in the old snapshot gains siblings while the original symbol
survives โ distinguishing it from a plain signature change (remove+add)
and from an unrelated same-leaf name in a different scope. The relational
signal (a name that was unique gained a sibling) is what a plain
addition/removal diff can't express โ without overload-set awareness, this
is indistinguishable from a harmless new function.
Runtime failure demonstration¶
Severity: INFORMATIONAL
Scenario: old binaries are untouched โ the demo shows binary compatibility holding, with the hazard waiting at recompile time.
# Build old library + app
g++ -shared -fPIC -g v1.cpp -o liblib.so
g++ -g app.cpp -L. -llib -Wl,-rpath,. -o app
./app
# โ to_celsius(98.6f) = 37.0000 (expected 37.0)
# Swap in new library (no recompile)
g++ -shared -fPIC -g v2.cpp -o liblib.so
./app
# โ to_celsius(98.6f) = 37.0000 (expected 37.0) โ binary-compatible
# The break is at recompile time, against the v2 header:
cat > amb.cpp <<'SRC'
#include "v2.h"
int main() { auto fp = &units::to_celsius; (void)fp; }
SRC
g++ -c amb.cpp -I.
# โ error: unable to deduce 'auto' from '& units::to_celsius'
Why INFORMATIONAL, not CRITICAL: no existing binary crashes or
misbehaves. The silent hazard is at the next recompile: rebuilding the
original app source against v2 headers binds to_celsius(98.6f) to the new
float overload โ the result is computed in float precision from then
on, with no diagnostic anywhere.
Safe redesign¶
- Name the new function instead of overloading (
to_celsius_f(), or a template with explicit constraints) when the existing name has shipped as unique. - If the overload must land, document the re-routing in release notes
and grep consumer code for address-taking (
&to_celsius) and float-argument call sites. = deletethe risky overload direction when the goal is to forbid lossy calls rather than add a fast path (float to_celsius(float) = delete;makes float callers explicit instead of silently re-routed โ that deletion is its own API break, but a loud one).
Real-world example: the KDE Frameworks binary-compatibility policy
("you can... add new non-virtual functions but note that adding an
overload to a function that previously had none can break source
compatibility") is the canonical write-up. std::filesystem::path
construction and std::to_chars overload growth both triggered exactly
this class of downstream deduction breakage during standard-library
evolution.
References¶
- KDE ABI Policy โ "adding new overloads" bullet
- Itanium C++ ABI โ function mangling encodes parameter types
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hv2.cppv2.h
See also: Examples overview ยท All COMPATIBLE_WITH_RISK cases ยท Category: Risk.