The C++ Standard Library Has Been Walking Itself Back for Fifteen Years, and the Receipts Are Public
Published: May 23, 2026
The C++ standard library has been walking itself back for fifteen years, and the receipts are public
Sandor Dargo's post this month on std::copyable_function closes with a quick-reference table. Four callable wrappers, one recommendation each, and at the bottom of the list one entry that should stop any working C++ engineer cold:
std::function: Legacy. Avoid in new code.
std::function shipped in C++11. The committee spent fifteen years shipping the wrappers that should replace it. The latest, std::copyable_function, lands in C++26. The recommendation written on top of the new arrival is not "use this when you need a copyable callable." It is "do not use the original."
This is not unusual. The C++ committee has been writing that sentence about its own features since C++11 was new. Sometimes the sentence is formal (a paper number, a deprecation in the standard, a removal one cycle later). Sometimes the sentence is what every senior engineer tells every junior engineer on day one ("never reach for that, here is what to use instead"). And sometimes the sentence cannot be written into the standard at all, because the broken thing is locked in by ABI compatibility, so it stays in the standard library as the default that every tutorial reaches for and every production codebase quietly replaces. The pattern is so consistent that it deserves its own catalogue, with paper numbers next to every entry, so the next time someone tells you the new C++ feature is the future you can ask them to estimate how long until the next paper deprecates it.
This piece is that catalogue, in three tiers. The first tier is the formal walk-backs the committee has written down. The second tier is the "everyone knows to avoid this" walk-backs that the committee has not formalised. The third tier is the most damning, because it is the standard library containers that almost every C++ codebase uses every day and that the committee cannot fix without breaking ABI. We have receipts on the third tier from our own Rust-vs-C++ multi-book benchmark, which measured 58 times the P99 latency between Rust's standard library and C++'s on identical workloads with identical isolation, and which traced the gap to three containers the committee has never formally said are broken.
Tier 1: The formal walk-backs the committee has written down
Every entry below points at a real paper that the working group adopted. None of these are arguments. They are admissions in writing.
The cleanest historical case is std::auto_ptr, the C++98 smart pointer whose copy-as-move semantics broke generic code and standard containers from the day it shipped. Deprecated in C++11, removed in C++17 by N4190 "Removing auto_ptr, random_shuffle(), And Old <functional> Stuff", Stephan T. Lavavej. That single paper also took out the entire <functional> adapter zoo from C++98: std::bind1st, std::bind2nd, std::ptr_fun, std::mem_fun, std::mem_fun_ref, std::unary_function, std::binary_function, std::pointer_to_unary_function, std::pointer_to_binary_function. The replacement is the lambda, a language feature that landed two cycles earlier and made the entire adapter framework irrelevant. std::random_shuffle went with them, deprecated in C++14, removed in C++17, replaced by std::shuffle because the original depended on std::rand and global state.
Dynamic exception specifications (throw(X, Y)) were the C++98 mechanism for declaring which exceptions a function could throw. Deprecated in C++11, removed in C++17 by P0003R5, Alisdair Meredith. Replacement: noexcept. The vestigial throw() synonym for noexcept(true) survived until C++20, when P1152 finally killed it. Eighteen years of standard text spent un-shipping an exception model.
std::iterator, the C++98 base class every "Effective C++" book taught you to inherit from, was deprecated in C++17 by P0174R2 ("Deprecating Vestigial Library Parts in C++17", Meredith). Removal is now proposed for C++26 in P3365R1. The replacement is "define the five typedefs yourself", which is what most engineers were doing anyway because inheriting from std::iterator never gave you anything useful.
std::aligned_storage and std::aligned_union shipped in C++11, were deprecated in C++23 by P1413R3 (CJ Johnson, Google). The paper's rationale is worth quoting because it captures the committee admitting a design error on its own work. The deprecated types require typename ::type boilerplate, require reinterpret_cast to access the contents, treat Len == 0 as undefined behaviour, and are not constexpr. The replacement is "use alignas(T) std::byte[sizeof(T)] directly", which is what the standard probably should have shipped in the first place.
std::not1/std::not2 and the unary_negate/binary_negate adapters were deprecated in C++17, removed in C++20, replaced by std::not_fn (P0005). std::get_temporary_buffer and std::raw_storage_iterator were deprecated in C++17 by P0174 and removed in C++20 by P0619. The register keyword was deprecated in C++11 and removed in C++17 by P0001. Trigraphs were removed in C++17 after thirty years of being a wart on the language.
The most embarrassing entry in the formal-walk-back catalogue is the C++11 garbage collection interface. The committee shipped std::declare_reachable and friends in C++11. No major implementation ever provided a real garbage collector behind those entry points. The interface was removed in C++23 by P2186R2 (JF Bastien), having been added and removed without ever once functioning as advertised. Twelve years of standard text spent un-shipping a feature nobody used.
Then there are the Technical Specification rollbacks, the parts of the standardisation pipeline that the committee outright rejected before merging. The Concepts TS was redesigned for C++20 (P0734 family). The Modules TS was redesigned via the merged-modules proposal P1103R3. The Coroutines TS was substantially modified before adoption. The Reflection TS was rejected entirely and replaced by P2996 for C++26, a completely different value-based design. The Executors TS went through multiple rejected rounds before becoming P2300 sender/receiver in C++26. The Networking TS has been deferred so many times that as of C++26 it is still not in the standard. Every one of those is the same admission written differently: "the previous design did not work, here is the next one."
And finally the trigger of this whole article. std::function shipped in C++11. Its const operator() invokes non-const callables, which is a const-correctness defect that has been part of the standard for fifteen years and cannot be fixed without breaking ABI. The committee's response has been to ship a sequence of replacements in adjacent cycles: std::move_only_function in C++23 via P0288R9, std::copyable_function in C++26 via P2548R6, and std::function_ref in C++26 via P0792R14. The original std::function is still in the standard. Dargo's table tells you to avoid it. So does every working C++ codebase you can audit.
Tier 2: The "everyone knows to avoid this" walk-backs the committee has not written down
These features are still in the standard. None of them are formally deprecated. Every senior C++ engineer in the industry will tell every junior engineer to avoid them on the first day of the job.
std::regex shipped in C++11. The committee's own paper P1844R1 records, in writing, that "the C++ committee noted that std::regex performance is very poor relative to other available solutions" and discouraged spending implementation effort on it. The standard library shipped a feature whose primary documented quality is that the committee acknowledges it is too slow to use. The replacement is Hana Dusíková's CTRE (compile-time regular expressions), with a standardisation attempt in P1433R0. Outside the standard the replacement is Boost.Regex, RE2, or PCRE2. Production code uses one of those. The standard std::regex exists for tutorials.
std::async shipped in C++11. The destructor of the returned future blocks until the async operation completes. N3679 documents the resulting deadlock trap. The replacement is the entire sender/receiver effort that finally landed in C++26 via P2300, fifteen years after std::async first shipped broken. In the meantime, every working low-latency codebase uses thread pools, std::thread directly, or platform-specific async primitives. std::async exists in the standard so that introductory textbooks have something to write about.
<iostream> shipped in 1998. It is slow, locale-bound, thread-unsafe for formatting, and produces error messages that are widely considered a hazing ritual for new C++ engineers. The committee shipped P0645 std::format in C++20 and P2093 std::print / std::println in C++23. Neither deprecates <iostream>. The committee will not write that sentence. It is also the sentence every working engineer is told as soon as they ship printf-style debug output in a code review.
std::list is the canonical entry in this tier. Bjarne Stroustrup spent the 2012 GoingNative keynote showing that std::vector beats std::list even for the textbook "insertion in the middle of a large container" workload, because the linear scan dominates and the pointer chase punishes the cache. The follow-up post is titled, with deliberate emphasis, Are lists evil?. The answer is yes. std::list is not deprecated. It exists in the standard. Every working C++ engineer is told never to reach for it.
std::deque is the next entry. The Microsoft STL maintainers have a public issue, microsoft/STL#147, titled "<deque>: Needs a major performance overhaul", acknowledging that the standard's mandated block size is too small and the design needs to be rebuilt at the next ABI break. Until then, std::deque ships in every standard library with the same poor cache behaviour everyone has known about for twenty years.
std::valarray shipped in 1998 as a numeric container with expression-template optimisation potential. The optimisation work was never done. The current cppreference text says implementations "don't appear to have any special code" beyond a plain container. Eigen, xtensor, and Blaze fill the niche. std::valarray is in the standard for archaeological reasons.
std::vector<bool> is the famous case. Howard Hinnant's On vector<bool> is the canonical analysis. The bit-packed storage is genuinely useful; the problem is that the type is named like a std::vector specialisation and silently fails to satisfy the std::vector interface, so generic code that takes a vector<T>& does the wrong thing when T = bool. The fix would be a rename, which the committee will not do, so the trap remains in the standard. Every working engineer learns to write std::deque<bool> or to use a different container.
std::shared_ptr's default atomic reference count, the std::initializer_list interaction with auto, the std::function implementation-defined small-buffer optimisation that produces different runtime performance on libstdc++, libc++, and MSVC STL, the std::random_device that the standard explicitly permits to be deterministic. Each of these has named expert commentary calling the design a defect, and none of them are formally deprecated. They are the standard library you ship to production and route around with your own helpers, your own conventions, and your own code-review rules.
The volatile saga deserves its own line. volatile was deprecated for compound operations and parameters/returns in C++20 by P1152R4, partially un-deprecated in C++23 by P2327R1 after embedded-community pushback, with further deprecation removals scheduled by P2866R0 for C++26. The committee deprecated a feature, the affected community objected, the committee walked the deprecation back, and now there is a paper to walk part of the walk-back back. This is what fifteen years of standards work on a five-letter keyword looks like.
Tier 3: The containers everyone uses and nobody can fix
The most damning tier is the standard library containers that the C++ committee cannot deprecate because they are the defaults that every textbook teaches and that ABI compatibility freezes in place. These are the containers a C++ beginner reaches for on the first day of writing real code. Three of them are demonstrably wrong by the standards everyone else has agreed on. We have receipts.
std::unordered_map is the canonical case. The C++11 specification mandates bucket and iterator stability that effectively forbids open addressing, which is the cache-friendly hash-table architecture everyone else has standardised on for fifteen years. Matt Kulukundis's CppCon 2017 talk Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step showed Google's SwissTable architecture beating std::unordered_map by roughly 3x. Folly's F14, Boost's unordered_flat_map, ankerl::unordered_dense, and Martin Ankerl's other variants all win by similar margins. Rust's HashMap, which uses the hashbrown SwissTable port, is the cache-friendly architecture by default in the standard library. The C++ committee added std::flat_map and std::flat_set in C++23 via P0429R9 but cannot fix std::unordered_map itself. The defaults stay broken.
std::map and std::set are red-black trees, node-based, with one heap allocation per node and pointer-chasing on every traversal. B-trees have beaten red-black trees for in-memory containers on real hardware since roughly the time the iPhone shipped. Abseil's btree_map, Boost's flat_map, and Rust's BTreeMap are all B-tree based. The C++ committee added std::flat_map in C++23, did not deprecate std::map, and the default sorted associative container in the language is still a red-black tree.
std::list is the third container, and it is the worst. We covered this in Tier 2; the receipt comes from our own benchmark and is worth the line.
Our multi-symbol order book benchmark ran the same workload, the same seed, the same isolated cores on aorus-class hardware, with two naive implementations. The C++ version used std::unordered_map, std::map, and std::list. The Rust version used HashMap, BTreeMap, and VecDeque. The P99 numbers in cycles per operation:
| Implementation | P99 cycles |
|---|---|
C++ naive (unordered_map + map + list) |
302,653 |
C++ step 1 (flat_hash_map + map + deque) |
9,951 |
C++ step 2 (flat_hash_map + btree_map + deque) |
9,114 |
C++ step 3 (flat_hash_map + btree_map + vector) |
4,268 |
Rust naive (HashMap + BTreeMap + VecDeque) |
5,177 |
Fifty-eight times the P99 latency between the two naive implementations. The composition of that 58x is the part that bears on the walk-back thesis. The std::list to std::vector switch alone is roughly 70x. The std::unordered_map to flat_hash_map switch is 3 to 5x. The std::map to btree_map switch is 1.09x and inside the noise. Three for three on the containers in the comparison, the C++ standard library's default is the slow one, and the slowness is large enough to swamp every other factor in the benchmark.
The point is not that Rust the language is 58x faster than C++. The point is that Rust's standard library shipped with the right defaults the first time, and C++'s standard library shipped with three known-bad defaults that the committee cannot fix without breaking ABI. The Rust beginner reaches for BTreeMap and VecDeque and gets a 5K P99. The C++ beginner reaches for std::map and std::list and gets a 300K P99. The difference between the two is structural. The C++ committee knows it is structural. The fix is not formally on the table.
Why this keeps happening
The pattern under all three tiers is the same. The C++ standard library is a permanent accretion of designs that the field has subsequently learned were wrong. The committee, to its credit, sometimes admits the error in writing and ships the deprecation paper. Sometimes the error is too widely deployed to deprecate, so the new feature ships alongside the old and the field learns by oral tradition which one to use. And sometimes the error is in the ABI of the containers everyone uses, in which case the committee cannot fix it at all and the standard library remains a museum of bad defaults that you are expected to know not to use.
This is not a critique of the committee's individual members, most of whom are doing the hardest possible job in the most thankless possible venue. It is a critique of the structural commitment to never break existing code, which means the slow containers, the broken regex, the deadlocking async, the costless auto_ptr admission, the costless aligned_storage admission, all of it stays. The standard library is now a layer cake of fifteen years of "do not use that, use this instead", and the working engineer's job is to know the dates of the bad layers.
The Vasa problem: features nobody asked for, on top of features nobody can remove
There is a second pattern under the walk-backs that deserves its own section because it explains why the layer cake keeps growing. The committee is not only failing to remove bad features. It is also continuously adding new ones that no working engineer asked for, championed by individuals who get professional recognition for shipping the proposal, and the result is a language whose surface area expands faster than any single team of implementers can keep up with.
The strongest statement of this critique comes from inside the committee itself. Bjarne Stroustrup's 2018 paper "Remember the Vasa!" (P0977R0) opens with the sinking of the Swedish warship Vasa in 1628, top-heavy with cannons and ornate sculptures that each made sense in isolation, capsizing twenty minutes into its maiden voyage. Stroustrup's diagnosis of WG21 is direct: "We now have about 150 cooks; that's not a good way to get a tasty and balanced meal." His most quotable line is the one every working engineer should keep pinned: "Hardly any paper contains extensive discussions of the proposed feature's effect in combination with other new features, existing features, and libraries in 'ordinary code' written by 'ordinary programmers.'" The paper is not a casual blog post. It is a numbered WG21 document from the man whose name is on the language, telling the committee that the feature pipeline is failing to ask "what does this do to the system."
std::simd is the canonical instance of the pattern. We covered the technical case in std::simd Is a Solution to the Wrong Problem so we will not rehearse it here, but the structural shape is exactly what Stroustrup warned about. One researcher (Matthias Kretz at GSI Helmholtz, building the Vc library since around 2010) spent a decade shepherding P0214 through nine revisions, into the Parallelism TS 2, then into C++26 via P1928. By the time the proposal landed, the world outside the committee had built Google Highway, ISPC, EVE, xsimd, and SIMDe, and the auto-vectorizer in GCC and Clang had improved to the point that scalar loops beat std::simd on -O3 -march=native. The committee shipped a library that compiles ten times slower than equivalent scalar code, runs slower than the auto-vectorizer it was supposed to replace, cannot express ARM SVE's scalable-width vectors, and has no runtime dispatch story. No major user requested it. No production codebase is converting to it. The only group that benefits is the proposers, whose committee work now carries the prestige of "got a feature into C++26", and the field of consultants and trainers who will now teach courses on it.
The motive question is hard to litigate honestly, so we will not. Maybe the proposers genuinely believed they were solving a problem. Maybe the committee genuinely believed the field would adopt the result. What is not hard to litigate is the outcome: the feature shipped, nobody uses it, and the standard library now carries it forever because of the ABI commitment described above. That is the Vasa problem in operational form. Each cannon makes sense in isolation. The ship is what cannot stay upright.
The cost of every such feature compounds in places the proposer does not pay. The three standard library implementations (libstdc++, libc++, and MSVC STL) are each maintained by single-digit teams of engineers, and the maintenance surface they have to cover doubles roughly every two standards cycles. Jonathan Wakely carries most of libstdc++ on his own back. Stephan T. Lavavej and a small group inside Microsoft carry MSVC STL. libc++ has had several maintainers come and go. Every new feature shipped means new test matrices, new conformance bugs, new interaction surfaces with every other feature in the same standard, and new entries in the bug tracker that the next generation of maintainers will inherit. std::regex has been known-broken for fifteen years because the implementers do not have the cycles to fix it. std::deque carries the microsoft/STL#147 issue acknowledging the design needs a rebuild that nobody has time to do. C++20 modules still do not work cleanly across all three implementations six years after the standard shipped. The committee adds; the implementers absorb; the ABI commitment then prevents anyone from ever cleaning up.
The downstream effect on accessibility is also worth naming, even if we cannot prove intent. Whether or not anyone designed it this way, the modern C++ standard has reached a complexity level where genuine working knowledge of it is restricted to a small population of full-time specialists. The senior engineer at a tier-one shop spends years learning the dates of the bad layers, the workarounds in the third-party libraries, the differences between the three standard library implementations, the parts of the standard that work in theory and the parts that work in practice. The result is a labour market in which "C++ engineer" is a much higher-paid job than "engineer who can program", because the C++ part requires a museum docent's familiarity with fifteen years of accumulated walk-backs and Vasa features. Whether this is a side effect or a feature of how the committee operates is a question we will leave open. The labour-market signal is real either way: the salary band for senior C++ engineers in HFT, search, browsers, and operating systems is roughly twice the equivalent band at companies using languages where the standard library does not require a curriculum.
A field where the senior engineers' value is partly bounded by their ability to navigate the language's own museum is not a field that has won. It is a field in which the museum has become the moat, the moat protects the senior engineers, and the next round of features lands on top of the moat for the next round of senior engineers to learn. The Vasa paper diagnosed this in 2018. The pattern has continued.
Is this actually a C++-specific problem? A fact-check against other languages
A fair question, because the obvious response to everything above is: every language ships features and later regrets them. Python had the 2-to-3 break. Java has a deprecation tooling system with @Deprecated(forRemoval=true). Rust has editions. C# walked away from CAS, Remoting, and BinaryFormatter when it moved from .NET Framework to .NET Core. If "shipped and regretted" were a unique C++ failure mode, the rest of the field would not have receipts. So let us look at the receipts.
Python deleted twenty-plus standard library modules in a single PEP. PEP 594 removed aifc, asynchat, asyncore, audioop, cgi, cgitb, chunk, crypt, imghdr, mailcap, msilib, nntplib, nis, ossaudiodev, pipes, smtpd, sndhdr, spwd, sunau, telnetlib, uu, xdrlib. PEP 632 removed distutils in 3.12. The official policy in PEP 387 gives the Steering Council the explicit power to shorten the deprecation cycle for "dangerously broken or insecure features." Python deletes things from the standard library on a published schedule and ships an interpreter version that does not include them.
Java has a more drawn-out process but it ends in actual removal. The Applet API journey is the canonical case: JEP 289 deprecated it in Java 9 (2017), JEP 398 marked it for removal in Java 17 (2021), and JEP 504 actually removed it. Eight years end-to-end. The Nashorn JavaScript engine was added in Java 8, deprecated in 11, and removed in Java 15 via JEP 372. The SecurityManager, present since Java 1.0, was deprecated for removal in JEP 411 and permanently disabled by JEP 486; that JEP explicitly cites .NET removing Code Access Security as precedent. Java EE's javax.* packages were moved to Jakarta in Java 11 via JEP 320. Finalizers, Thread.stop, the Date/Calendar family superseded by java.time (JSR-310): all on the removal track or already gone.
Rust uses the edition system. 2015, 2018, 2021, 2024 are opt-in per crate via Cargo.toml. The 2018 edition reserved async and await as keywords; old code that used them as identifiers continues to compile under the 2015 edition by writing r#async. The standard library marks items #[deprecated] and ships replacements: mem::uninitialized was deprecated in 1.39 in favour of MaybeUninit; std::error::Error::description was deprecated in 1.42 in favour of source; the try! macro was deprecated for the ? operator. The edition system has limits the Rust documentation is explicit about (editions cannot change semantics or layouts of stdlib types, only syntax and lints), but for the class of mistakes editions can address, Rust addresses them by design.
C# went further. The .NET Framework to .NET Core transition was a hard major-version break that dropped BinaryFormatter, AppDomains, Remoting, Code Access Security, WCF server, and WebForms. Microsoft is willing to ship a net6.0 target framework that drops APIs and tell users to retarget. The async pattern itself was reshaped twice: from APM (BeginX/EndX, .NET 1.0) to EAP (event-based, 2.0) to TAP (Task/async/await, 4.5). Three patterns shipped, two are now vestigial, and Microsoft will tell you in writing which one to use.
JavaScript is the outlier in the other direction. TC39 essentially never removes anything because of the web-compatibility constraint, but it does withdraw proposals before standardisation: the cancelable promises proposal was withdrawn at Stage 1 by its champion in 2016, SIMD.js was abandoned in favour of WebAssembly SIMD, the "smart pipeline" debate has stalled the pipeline operator for years. JavaScript's escape valve is "do not ship it past Stage 4", not "remove it later", and the cost is that the language has accreted parallel mechanisms (var, let, const; CommonJS, AMD, ESM; callbacks, promises, async/await) that nobody can throw away.
Go is the most conservative. The Go 1 compatibility document is explicit: "programs written to the Go 1 specification will continue to compile and run correctly, unchanged, over the lifetime of that specification." Even there, io/ioutil was deprecated in Go 1.16 and the functions redirected to io and os, but the package still exists because the promise forbids actual removal. Go's design choice is to live with the mistakes rather than ship the breakage. C++'s situation is the same outcome arrived at by a different mechanism.
So the comparison is not that other languages do not ship mistakes. Every language on the list has shipped and regretted features. The comparison is retention rate. Python deleted twenty modules in one PEP. Java removed Nashorn, removed Applets, and disabled the SecurityManager. .NET dropped CAS in a major version break. Rust retires syntax through editions. Even Go's deprecation of io/ioutil is a documented event. C++ has shipped std::regex, std::unordered_map, std::vector<bool>, std::valarray, the broken std::function const-correctness, and a long list of architectural choices that the field has subsequently agreed are wrong, and almost none of them have been removed.
The reason is structural and the WG21 process has acknowledged it in writing. P1863R1 "ABI - Now or Never" put the question to the committee in Prague in 2020: either commit to an ABI break for C++23 or commit to permanent ABI stability. The committee voted, in effect, for permanent ABI stability. That vote is why std::regex cannot be fixed. It is why std::unordered_map cannot be made open-addressing. It is why std::list and std::map and std::deque cannot be changed even though their performance has been known to be wrong for a decade. The standard library's ABI is enforced by the dynamic linker, not by polite request, because objects compiled against one libstdc++ release have to link cleanly with objects from another, and the layout of std::string and the membership of std::regex_traits are baked into deployed binaries forever. The Itanium C++ ABI specification and the libstdc++ ABI policy are the documents that make this concrete.
This is the structural argument and it is what makes C++ different. Python users pin python==3.12 and inherit exactly that interpreter's standard library. Rust users pick an edition in Cargo.toml. Java users target a JDK version. C# users target a net6.0 or net8.0 TFM. C++ users get whatever standard library their compiler frontend shipped, with the additional requirement that any library they link against agree, and there is no Cargo.toml for std::. There is no opt-in mechanism. -std=c++26 selects which headers exist and which language rules apply, but it does not give you a different std::string or a redesigned std::unordered_map. The standard library is the standard library, the linker enforces it, and the broken parts stay.
So the original irritation is half right. Every language ships things and walks them back. What C++ does uniquely is not get to walk them back. The mistakes that other languages can delete, deprecate, edition-out, or retire across a major version break stay in the C++ standard library essentially forever, because the alternative is breaking the linker contract that the entire C++ ecosystem depends on. That is the C++-specific part of the pattern, and it is the part working engineers actually feel: the standard library you ship to production in 2026 still contains, by design and by enforcement, every wrong default the committee has accepted since 1998.
There is a reason the modern C++ codebase that ships in any tier-one trading firm, search engine, or browser is built mostly out of non-standard libraries. Boost, abseil, Folly, EASTL, Chromium's //base, hand-rolled containers, custom allocators, Hana's CTRE, Niall Douglas's Outcome, Lewis Baker's coroutine library. The standard library is the thing you avoid as much as you use. Every senior engineer learns this. The juniors find out the first time they reach for std::list in a code review and the senior gently asks them to use std::vector instead.
Dargo's line is exactly right. std::function is legacy. Avoid it in new code. The point of this article is that the sentence is fifteen years old and counting, that the C++ standard library has been making it true about its own contents for the entire post-C++11 era, and that the next time the committee ships a shiny new replacement for something it shipped a decade ago, the honest read of the announcement is that the older design did not work and now there is a paper saying so. The new feature is welcome. The pattern is what you should learn to read.