Why I Do Not Like C++

I want to be precise about what this post is and is not. It is not an argument that C++ is broken or that the people who use it are wrong or that nothing good has ever been built with it. Enormous amounts of valuable software are written in C++ and some of the sharpest engineers I respect use it daily.

It is an argument that C++ is not for me and an honest account of why.

It Never Stops Growing Link to heading

C++ started as “C with Classes” in 1979. It has not stopped accumulating features since. Every revision of the standard — C++11, C++14, C++17, C++20, C++23 — adds more. Templates, lambdas, concepts, coroutines, modules, ranges. Each addition is motivated by a real problem. None of them are removed.

The result is a language that is effectively several languages layered on top of each other, with no mechanism for shedding the ones that turned out to be mistakes. A C++ codebase written in 2005 looks nothing like one written in 2020, and both are valid C++. You cannot read the language fluently; you have to read whichever dialect the project chose, and then the subset of that dialect the original author preferred.

I want to understand the code I am reading completely. C++ makes that unreasonably difficult.

The Cost of Abstraction Is Hidden Link to heading

C++ offers high-level abstractions — smart pointers, containers, algorithms, ranges — that are designed to be zero-cost in theory. In practice, the cost is not zero; it is just invisible. Implicit conversions, template instantiation, copy constructors, move semantics: things happen that you did not ask for and may not notice until you are reading assembly output wondering where those instructions came from.

I have spent years doing systems programming and UNIX systems administration. I want to know what the machine is doing. When I write code, I want a clear and honest relationship between what I typed and what executes. C++ does not offer that. The abstraction stack is deep and the semantics are full of surprises.

Zig has a phrase for this: no hidden control flow. It is written down as an explicit design goal. C++ has the opposite design goal and calls it convenience.

Error Handling Is a Museum Exhibit Link to heading

C++ has three error handling mechanisms: return codes, exceptions and std::expected (added in C++23). They coexist without replacing each other. Legacy code uses return codes. Library code throws exceptions. Modern code uses std::expected. Code that calls all three has to juggle all three conventions simultaneously.

Exceptions in particular are something I find genuinely difficult to defend. They introduce non-local control flow that is invisible at the call site. A function call that looks ordinary can throw, and whether it throws, what it throws, and what state the program is in afterward is something you have to discover by reading every function in the call stack. Zero-cost exception handling is not zero-cost when an exception is thrown; it is just cost-deferred.

Go handles this with explicit return values. Zig handles it with error unions. Both approaches put the failure path in the source, next to the code that can fail, where you can read it. C++ hides it.

The Build System Problem Link to heading

C++ does not have a standard build system. It has make, cmake, bazel, meson, ninja, scons, and others. Each project picks one. Each one has its own language and its own failure modes.

This is not a minor inconvenience. Building a non-trivial C++ project from source frequently means debugging the build system before you can debug the program. Header file ordering, include paths, linking order, platform differences — all of it falls on you, with no standard tooling to help.

Go ships with go build. Zig ships with a build system written in Zig itself. Both work reliably on day one. C++ asks you to assemble your own toolchain and then maintain it.

Templates Are a Language Inside the Language Link to heading

C++ templates are Turing-complete. That sounds impressive until you realize what it means in practice: template metaprogramming is a separate programming language, with different syntax, different error messages, and different rules, embedded inside C++. The error messages it produces when something goes wrong are notoriously incomprehensible.

Concepts, introduced in C++20, improve this. But they do not replace the underlying template machinery; they add constraints on top of it. The complexity does not go away.

Zig comptime solves the same class of problems — generic code, compile-time computation, type-level logic — using regular Zig. You use the same syntax, the same control flow, the same mental model. When something goes wrong, the error message describes a Zig problem, not a template instantiation failure.

What C++ Does Well Link to heading

C++ is everywhere. The job market for C++ is large. The ecosystem is mature. Performance-critical software — game engines, financial systems, compilers, databases — is routinely written in C++ by people who know it deeply and use it well.

If you need to work with an existing C++ codebase, you will learn C++. If you are targeting a platform where C++ is the only practical option, you will use C++. These are not reasons to love the language; they are reasons to be pragmatic.

Conclusion Link to heading

C++ solves real problems. It has survived and grown because it keeps solving them, in an industry that moves fast and leaves old code behind slowly.

But every problem C++ solves, it solves with more language. More syntax, more rules, more edge cases, more things to hold in your head. That is a tax I am no longer willing to pay.

I prefer languages that solve problems by removing things rather than adding them. C++ and I want different things from a programming language, and that is fine.