C++ ABI Constraints on std::regex and std::string_view
The C++ standardization committee’s commitment to Application Binary
Interface (ABI) stability preserves binary compatibility between
compiled translation units, but it also prevents meaningful
architectural improvements to existing standard library components. Both
std::regex and std::string_view demonstrate
the cost of this trade-off. Proposals to overhaul the notoriously poor
performance of std::regex and refine the memory layout or
calling conventions of std::string_view faced
insurmountable opposition because modifying internal member variables,
object sizes, or function signatures alters the compiled machine code
interface, breaking interoperability with previously compiled binary
libraries.
An ABI dictates low-level details that source code does not
explicitly define, including the memory layout of classes (member
variable order and alignment), class sizes, calling conventions (such as
how arguments are passed in registers versus on the stack), and name
mangling. When a standard library implementation (such as GNU
libstdc++, LLVM libc++, or Microsoft MSVC STL)
ships a class, its ABI becomes fixed. If a new version of the standard
alters that class's data layout, linking newly compiled code with an
older precompiled dynamic library (.so or
.dll) causes undefined behavior, often manifesting as
memory corruption, silent data corruption, or segmentation faults.
The primary issue with std::regex stems from its design
and initial implementation choices dating back to TR1 (Technical Report
1) and C++11. Many standard implementations of std::regex
are orders of magnitude slower than alternatives such as Google's RE2,
PCRE, or modern compile-time regular expression engines. The performance
issues arise from suboptimal state-machine representation, recursive
parsing techniques, and excessive heap allocations for match state
tracking.
Fixing std::regex requires completely replacing its
internal representation, changing member data structures, or altering
the layout of helper types like std::match_results. Doing
so changes the sizeof(std::regex) and the offsets of its
members. Because standard vendors refused to introduce an ABI break that
would invalidate entire software ecosystems relying on precompiled code,
the committee could not adopt structural fixes. As a result,
std::regex remains virtually unfixable in place; any real
performance replacement would have to be standardized under a distinct
name (such as a hypothetical std::regex2).
While std::string_view was introduced much later in
C++17, it encountered a different set of ABI constraints related to
hardware efficiency and calling conventions. Conceptually,
std::string_view consists of two pieces of data: a pointer
to a character array and a size (std::size_t). The System V
AMD64 ABI (used by Linux and macOS) dictates how two-word structures are
passed into CPU registers. The exact specification of trivial
copyability, structure layout, and destructor behavior impacts whether a
std::string_view can be passed efficiently in two CPU
registers or must be passed indirectly through memory.
During and after the standardization of
std::string_view, proposals were discussed regarding
alternative representations, null-termination aware variants, and
optimizations for passing views to functions. Furthermore,
inconsistencies across compilers in how small, aggregate-like classes
are passed in registers created friction. Changing
std::string_view's member layout, adding flags (such as
whether the referenced string is null-terminated), or changing its
triviality would alter whether callers and callees agree on how
registers are utilized. If a library compiled under an older standard
passed a string_view through memory while a newer caller
passed it in registers, function calls would immediately fail.
The standoff reached a turning point during the WG21 Prague meeting
in 2020, where the C++ committee took an indicative vote against
breaking ABI for the C++23 cycle. This decision cemented the reality
that standard library types cannot mutate their binary footprint to gain
performance or modern ergonomics once released. For both
std::regex and std::string_view, ABI
preservation means that historical design flaws and performance
bottlenecks must be bypassed through new types rather than repaired in
the existing abstractions.