Skip to content

Case 32 โ€” Parameter Default Value Changes (C++)

Field Value
Verdict ๐ŸŸ  API_BREAK
Category API Break
Platforms Linux
Flags API break
Detected ChangeKinds param_default_value_changed, param_default_value_removed
Source files examples/case32_param_defaults/

Category: C++ Defaults | Verdict: ๐ŸŸ  API_BREAK (with headers) / NO_CHANGE (object/ELF-only)

A parameter default is removed (configure(verbose, ...)), which is a source-level break โ€” callers relying on the default no longer compile. The mangled symbol is unchanged, so the binary ABI is intact. Default-argument values live only in the header, so this is detected only in header (castxml) mode (param_default_value_removed / param_default_value_changed); object/DWARF comparison reports NO_CHANGE.

What changes

Method v1 signature v2 signature Effect
connect void connect(int timeout = 30) void connect(int timeout = 60) Default changed
configure void configure(bool verbose = true, int retries = 3) void configure(bool verbose, int retries = 5) verbose lost default; retries changed
disconnect void disconnect(int code) void disconnect(int code = 0) Default added

Why this is NOT a binary ABI break

In C++, default parameter values are resolved at the call site during compilation. When the compiler sees conn.connect(), it rewrites it to conn.connect(30) using the default from the header at compile time. The library's .so never knows about default values โ€” it only receives the actual arguments.

This means:

  1. Default changed (timeout 30 -> 60): Binaries compiled against v1 will always pass 30. Only code recompiled against v2 will pass 60. No binary break.

  2. Default removed (verbose): Binaries compiled against v1 already have true baked in. Only recompilation against v2 would fail (source break) because configure() with zero args is no longer valid.

  3. Default added (disconnect): Existing binaries already pass explicit values. New code compiled against v2 can call disconnect() without arguments. Fully backward compatible.

The mangled symbol names are identical (_ZN10Connection7connectEi, etc.) because default values do not participate in name mangling.

Code diff

 class Connection {
 public:
-    void connect(int timeout = 30);
-    void configure(bool verbose = true, int retries = 3);
-    void disconnect(int code);
+    void connect(int timeout = 60);
+    void configure(bool verbose, int retries = 5);
+    void disconnect(int code = 0);
 };

Real Failure Demo

Severity: NONE (binary compatible)

Scenario: Compile app against v1 headers, swap in v2 .so.

# Build v1 library + app
g++ -shared -fPIC -g v1.cpp -o libfoo.so
g++ -g app.cpp -I. -L. -lfoo -Wl,-rpath,. -o app
./app
# โ†’ Parameter defaults demo (compiled against v1.hpp):
# โ†’
# โ†’ Calling connect() with default timeout:
# โ†’   Compiled as connect(30) from v1 header
# โ†’   OK โ€” v2 default is 60, but caller already passed 30
# โ†’
# โ†’ Calling connect(45) with explicit timeout:
# โ†’   OK โ€” explicit args are unaffected
# โ†’
# โ†’ Calling configure() with defaults:
# โ†’   Compiled as configure(true, 3) from v1 header
# โ†’   OK โ€” v2 removed verbose default, but caller already passed true
# โ†’ ...

# Swap in v2 (no recompile)
g++ -shared -fPIC -g v2.cpp -o libfoo.so
./app
# โ†’ Output is IDENTICAL โ€” defaults were baked into the caller at compile time.
# โ†’ The v2 library receives the same arguments as v1.

Source break verification (partial โ€” configure() with no args fails):

# Create a minimal source that includes only v2.hpp and calls configure()
cat > /tmp/source_break.cpp << 'SRC'
#include "v2.hpp"
int main() {
    Connection conn;
    conn.configure();  // no args โ€” v2 requires explicit 'verbose'
    return 0;
}
SRC
g++ -g /tmp/source_break.cpp -I. -L. -lfoo -Wl,-rpath,. -o /tmp/app_v2 2>&1
# โ†’ error: no matching function for call to 'Connection::configure()'
# โ†’ note: candidate expects 2 arguments, 0 provided
#    (because 'verbose' lost its default in v2)
rm -f /tmp/source_break.cpp /tmp/app_v2

Reproduce with abicheck

g++ -shared -fPIC -g v1.cpp -o libfoo_v1.so
g++ -shared -fPIC -g v2.cpp -o libfoo_v2.so
abidw --out-file v1.xml libfoo_v1.so
abidw --out-file v2.xml libfoo_v2.so
abidiff v1.xml v2.xml
echo "exit: $?"   # โ†’ 0 (no binary ABI change)

How to fix

No fix needed for binary compatibility. For source compatibility:

  • Do not remove defaults from public headers in minor releases.
  • If a default value must change, document it clearly โ€” existing compiled binaries will silently continue using the old default until recompiled.
  • Consider using overloaded functions instead of defaults for critical parameters where behavioral differences matter.

References


Source files

  • CMakeLists.txt
  • app.cpp
  • v1.cpp
  • v1.hpp
  • v2.cpp
  • v2.hpp

See also: Examples overview ยท All API_BREAK cases ยท Category: API Break.