I've been a C programmer for most of my career. The kind who can feel what the CPU is doing. Move a register here, touch a block of memory there, shave off a microsecond. When you think at that level for long enough, you start to resent anything that calls itself "modern."
Not because you can't learn it. Because it feels wrong. Too many layers between you and the metal.
C with classes
For years, my C++ was really just C with classes. I found out later that most people who put "C++ engineer" on their resume are doing exactly the same thing. That's where you plateau, and it's a comfortable plateau. You ship code. It works. Nobody complains.
And a lot of people never leave that plateau. I'm not talking about junior developers. I'm talking about engineers with decades of C experience who never made the jump. The mental model of C is: I own every byte, I control every allocation, I decide when memory lives and dies. Accepting that a destructor will clean up for you, that you should stop calling delete, that std::unique_ptr knows better than you do when to free memory... that goes against everything a C programmer was trained to believe. Plenty of good engineers looked at that and said no thanks.
I almost did too. But then std::vector clicked. Then RAII clicked. Then I ran into compare_exchange_strong and compare_exchange_weak and spent a full day understanding when to use which one. Then C++17 arrived with SFINAE and template metaprogramming.
I questioned my life choices.
50,000 lines by hand
But I kept going, because the payoff was real.
My first serious Modern C++ project was a bridge layer between a trading platform and a strategy execution service. About 50,000 lines, took six months to write by hand. I picked C++ for speed and RAII, and the results justified the pain: on Windows 10, the process started at 22MB of memory, dropped to 11MB after running for a week. On Windows 11, 36MB at start, 12MB after a week. It was pulling tick data for every instrument at full frequency, the entire time.
That was the stage where I could use Modern C++. Vectors, smart pointers, move semantics, atomics. I'd crossed the first two hurdles: from C to C-with-classes, and from C-with-classes to C++11/14. Both were hard. Both filtered out a lot of people.
But those were hurdles you could clear on your own. Give a determined programmer enough time, and RAII will click. Move semantics will click. Smart pointers will click.
The third hurdle is different.
The pipe organ
A pipe organ is the most complex instrument ever built. Thousands of pipes. Four or five keyboards stacked on top of each other, called manuals. A pedalboard at your feet for the bass lines. Dozens of stops that change the sound of every pipe. To play it, you need both hands working different keyboards, both feet on the pedals, and somehow you also need to pull stops in the middle of a piece.
That's four hands' worth of work. You have two.
Modern C++ past C++17 is a pipe organ.
The vertical span alone is disorienting. At the bottom, you're still dealing with cache lines, branch prediction, and what the CPU is actually doing with your alignas(std::hardware_destructive_interference_size). At the top, you're writing concepts and consteval functions that execute entirely during compilation. You need to hold both levels in your head at the same time, because a one-line change at the top can restructure what happens at the bottom.
Then there's the depth. Every line of C++23 is a reverse derivation. std::expected<Value, Error> looks like one line. Behind it is a chain of compiler decisions about storage layout, copy elision, destructor sequencing, and exception-free error propagation that traces all the way back to what would have been fifty lines of C with manual error codes and goto cleanup blocks.
And the sheer width of the thing. Templates. Concepts. Coroutines. Ranges. Modules. PMR. SIMD intrinsics versus portable abstractions. constexpr versus consteval versus constinit. Even the experts specialize. A template metaprogramming wizard might not know the first thing about coroutine frame allocation. A SIMD specialist might never touch ranges.
The Swiss watch inside
Here's the thing people miss when they complain about C++ being too complex: this isn't a design failure. This is a price.
A mechanical watch movement has hundreds of components, machined to micron tolerances. It's absurdly complex. But it's complex because it chose to tell time without a battery, without a circuit board, without any external dependency. That constraint, total self-reliance with precision, is what forces the complexity. A quartz watch does the same job with a battery and a chip. Cheaper, more accurate, simpler. But the mechanical watch gives you something the quartz watch can't: it runs on nothing but itself.
Modern C++ made the same bargain. Zero-cost abstractions. Full hardware control. Compile-time safety. No garbage collector, no runtime, no VM. The language chose to give you everything, from register-level performance to type-level metaprogramming, in one system. That commitment to not compromise on any axis is what makes it so powerful. And it's exactly what makes it so hard to hold in one head.
The organ doesn't have five keyboards because the builder was a sadist. It has five keyboards because the music demands that range.
1,000 lines to 10
You want to see what that bargain looks like in practice? Look at the FIX protocol engine space.
QuickFIX was the industry standard for years. It was written in C++98/03 style, and the engineers who built it were not amateurs. To get acceptable performance, they had to hand-craft everything. A custom object pool: about 1,000 lines of carefully debugged code. A lock-free queue for market data: another 500 lines. Manual cache-line alignment to prevent false sharing: 200 more lines. Months of debugging and tuning before any of it was production-ready.
In C++23, the same functionality looks like this:
std::pmr::monotonic_buffer_resource pool_{64_MB}; // object pool
SPSCQueue<T, 4096> queue_; // lock-free queue
alignas(std::hardware_destructive_interference_size) // cache alignment
Three lines. Works correctly out of the box.
Or take tag lookup. In the QuickFIX era, you'd write a giant switch statement or build a std::unordered_map at startup. Fifty-plus cases, each a runtime branch, hundreds of lines:
std::string get_tag_name(int tag) {
switch (tag) {
case 8: return "BeginString";
case 35: return "MsgType";
case 49: return "SenderCompID";
// ... 50+ more cases
}
}
In C++23, you write a consteval function. The entire table gets computed during compilation. At runtime, looking up tag 35 is a single array index. No branches, no hash lookups:
consteval auto create_tag_table() {
std::array<TagEntry, MAX_TAG> table{};
table[8] = {TagInfo<8>::name, TagInfo<8>::is_header};
table[35] = {TagInfo<35>::name, TagInfo<35>::is_header};
// ...
return table;
}
inline constexpr auto TAG_TABLE = create_tag_table(); // zero runtime cost
Or SFINAE versus concepts. Constraining a session handler type in the old way required 200 lines of std::enable_if_t nested inside template parameter lists, producing error messages that no human could read. In C++23:
template <typename T>
concept SessionHandler = requires(T& h, const ParsedMessage& msg) {
{ h.on_app_message(msg) } noexcept;
{ h.on_send(std::declval<std::span<const char>>()) } noexcept -> std::same_as<bool>;
{ h.on_state_change(SessionState{}, SessionState{}) } noexcept;
{ h.on_error(SessionError{}) } noexcept;
};
Twenty-five lines. Reads like documentation. And when something doesn't satisfy the concept, the compiler says "T does not satisfy SessionHandler" instead of vomiting 500 lines of template substitution failure.
None of this means the QuickFIX engineers' work was wasted. The opposite. Their 1,000 lines of hand-crafted optimization became the blueprint for the next standard. std::pmr exists because people like them proved that custom allocators matter. Concepts exist because SFINAE was so painful that the committee had to find a better way. Every one-line C++23 idiom is standing on the shoulders of someone who wrote the 1,000-line version first.
But it also means that every line of C++23, that clean, compact, one-line call, is carrying the cognitive weight of those 1,000 lines inside it. The complexity didn't disappear. It got absorbed into the language. And now you need to understand what's happening beneath that one line, or you'll misuse it in ways that compile fine and fail silently at scale.
C++17 through C++23 didn't just raise the bar. They added three more keyboards to the organ. The instrument kept growing, and one person's hands didn't.
The planet I couldn't reach
Here's what that third hurdle looks like up close.
I have a set of compile-time sorting algorithms sitting in my code archive. QuickSort, MergeSort, HeapSort. All three run during compilation. Not at runtime. During compilation.
template<int... Vs>
struct arr {};
using input = arr<5, 3, 8, 1, 9>;
using sorted = quicksort_t<input>; // arr<1, 3, 5, 8, 9>
The input is a type. The output is a type. The sorting happens when the compiler processes your code, and at runtime the cost is zero.
To make this work, you need a full toolkit of compile-time operations: filter, concat, take, drop, merge, prepend, all implemented as template specializations. The QuickSort partitions around a pivot using template predicates. The MergeSort splits the type in half, sorts recursively, and merges with ordered comparison. Even the correctness checks are compile-time:
static_assert(std::is_same_v<quicksort_t<arr<5,4,3,2,1>>, arr<1,2,3,4,5>>);
static_assert(is_sorted_v<mergesort_t<arr<9,7,5,3,1,8,6,4,2,0>>>);
If any of those fail, the code doesn't compile. The tests run before the binary even exists.
I wrote these. It was not easy, not quick, and not something I could have figured out by reading cppreference for an afternoon. Template metaprogramming at this level is a different language wearing C++ syntax as a disguise. You're not writing instructions for the CPU. You're programming the compiler.
And this is one stop on the pipe organ. One. There's consteval, concepts, ranges, coroutines, modules, and every three years the language adds another row of pipes.
The registrant
Here's the thing about pipe organs: historically, the organist never played alone.
There was always a person next to them called the registrant. The registrant pulled stops, turned pages, managed the wind supply. Not because the organist was bad. Because the instrument required more hands than any human has.
Modern electronic organs solved part of this with combination actions: memory banks that store complete stop configurations. Instead of the registrant pulling twelve stops one by one between movements, the organist presses a single button and the entire registration changes instantly.
The registrant didn't make the organ simpler. The organ is exactly as complex. But the registrant made it playable.
AI is the registrant for Modern C++. And when you give it the right instructions, it doesn't just pull stops. It pulls the right stops.
When I started building NexusFix, a high-performance FIX protocol engine in C++23, I didn't just throw code at AI and hope for the best. I wrote a rulebook. Not vaguely. Specifically.
Mandatory patterns: C++23 standard compliance, zero-copy data flow with std::span and move semantics, compile-time optimization with consteval and constexpr, memory sovereignty through PMR pools and cache-line alignment, type safety with strong types and [[nodiscard]], deterministic execution with noexcept and no exceptions on hot paths.
Prohibited patterns: no new/delete on hot paths, no virtual functions in performance-critical code, no std::shared_ptr on hot paths, no floating-point for prices, no dynamic memory allocation during message parsing.
Forty-five numbered techniques, each mapped to specific source files. A six-phase optimization roadmap with measurable success criteria: zero hot-path allocations, cache miss rates below 5%, branch miss rates below 1%. A benchmark framework specifying exactly how to measure, down to RDTSC timing with lfence barriers and cache-line contention tests.
When AI has this kind of context, it doesn't guess about std::expected versus exceptions. The rulebook says no exceptions on hot paths, use std::expected, target deterministic control flow. The decision is already made. AI implements it correctly, in the specific codebase, following the established patterns.
The problem was never that AI couldn't write good C++23. The problem was that without constraints, it had to guess at hundreds of decisions that each require deep domain knowledge. Give it the constraints, and it stops guessing.
Remember those QuickFIX-era 1,000-line object pools? My rulebook has one line about them: "Use std::pmr::monotonic_buffer_resource for hot path allocation." AI reads that, implements the pool with pre-allocation and per-message reset, following the established memory patterns. Hot-path allocations dropped from 12 per message to zero. The 1,000 lines of knowledge that QuickFIX engineers accumulated over years is now compressed into one rule that AI can execute in an afternoon.
SIMD selection: I described the workload, AI prototyped implementations with raw intrinsics, Highway, and xsimd, all following the project's zero-copy and cache-alignment rules. xsimd won. The delimiter scan went from ~150ns to under 12ns. Thirteen times faster.
Compile-time lookup tables: the rulebook includes consteval protocol hardening. AI generated tag lookup tables from the FIX specification, replacing those 300 runtime switch branches the old way required, with compile-time verification that every entry was correct. Improvement ranged from 55% to 97%.
Each of these was a stop on the organ. With proper instructions, AI pulled them correctly.
What actually changed
When C++ reached C++17 and kept going, the language outgrew what one person could handle. The organ got more keyboards, more stops, more pipes. The music it could produce was extraordinary. But the number of hands you'd need to play it kept growing.
AI is the tool that lets us take Modern C++ back.
Not by making it simpler. C++23 is more complex than C++17, which was more complex than C++11. More features, more interactions between features, more ways to get subtly wrong results that compile without complaint.
What collapsed is the time between knowing and doing. "I know std::expected exists" to "I have a benchmarked, integrated implementation" used to take days. Now it takes hours. "I've heard of PMR" to "my hot path has zero allocations" used to take a week. Now it takes a day. The gap between reading about a C++23 feature and actually deploying it in production code has always been the widest in C++. Years wide, sometimes. Careers wide.
AI didn't close that gap. It made it crossable.
You still need to know what you're doing. If I didn't understand RAII, or what a cache line is, or why branch misprediction costs you 15 cycles, no amount of AI could help me write a meaningful rulebook. The organist still needs to know music. The registrant handles the logistics so the organist can focus on playing.
But here's what I learned: the registrant needs a score to follow. When I gave AI vague instructions, I got vague C++. When I gave it forty-five specific techniques, mandatory patterns, prohibited patterns, measurable success criteria, and a benchmark framework, it gave me code I could review and ship. The precision of the output matched the precision of the input.
The organ is exactly as complex as it was before. The music demands it. But with a registrant who knows the score, one person can play it again.
NexusFix parses FIX execution reports in 246 nanoseconds. Three times faster than QuickFIX. The hot path does zero allocations. The SIMD pipeline processes delimiters at 13x scalar speed. I built it in C++23, using AI as a constant collaborator on every technical decision, constrained by a rulebook that left nothing to chance.
The hardest part of Modern C++ was never the language. It was doing it alone.
You don't have to anymore.
The author builds high-performance C++ trading systems at SilverstreamsAI. NexusFix is an open-source FIX protocol engine in C++23.
He also writes The Ancient Mirror of Immortality, a hard sci-fi serial where C++ concepts are the laws of physics.
Top comments (0)