I have the impression he has listened to people like you and has come across significantly less negatively in the last couple years. Apart from that, there has always been a lot of substance, attesting only a superiority complex is not at all doing him justice in my opinion.
I for one love to watch and listen to him, not for the fact that I find myself agreeing to so much of what he says, but because he is able to express things in very insightful ways. And yes, I'm a sucker for that "no bullshit" attitude.
I have to use Outlook and Teams for work, and both have had a notification symbol on with "1" unread items for most of the past year, even though clearly everything has been looked at.
To me it's been a great aid as a rubber duck and general research tool. Totally agree that it can barely help coming up with a good architecture. But it's quite capable of understanding an existing or a suggested one, even somewhat capable (though clearly not surpassing the best humans) at evaluating it.
About progressive taxes. That would make the super rich not super rich. But it would not fix state finances. (Also of course it's not realistic to think that the super rich will just give 100% of their money for nothing in exchange).
If the state needs money, it needs to take it from where it is, which is still with the masses. In Germany, at around maybe 90k EUR income is a particularly bad place to be. You are nominally considered to be a good earner. But at that point you already give around 70% on the next EUR you earn for various taxes and social security. If you are financially responsible not only for yourself but for other people (which is not reflected in lower taxes unless you're legally married), you can still get along fine, but it might not feel like you are a good earner. I was in a similar situation at one point in my life, it had taken me hard work to earn that money, and I have to say I was a little pissed about the tax situation.
I think I understand why progressive tax systems exist. Given our societies' structures, they probably _have_ to exist. But also I wonder if long-term they _contribute_ to income and wealth inequality. It reduces incentives for those who earn less to earn more (which of course for many people is not easily possible, but for some it would be, by working more, working harder, looking for a different job, etc.). It also allows employers to spend less on their workforce, meaning you could understand it as a kind of subsidiary for employers. Of course, lower salaries might be the only way for employers to even offer a product at prices the market wants to pay. But again it also reenforces inequalities in the income structure.
That's just a matter of wrapping the allocator if you feel fhat is better approach. Question is, should there always be an allocator around with arr or do you want to split a build phase from an immutable use phase.
True. In C, it's hard to know what's defined where and what dependencies one is even using unknowingly. The only help is to build on multiple platforms from early on, this will reduce the pain of porting to new platforms later.
It's _very_ easy to write C that is impossible to port to other platforms or other compilers. The reason is that more than for most other ecosystems, there are many different implementations with their own implementations and quirks.
It's also easy to write C that won't build on the system it was originally developed on, a few years down the line. But that is true for almost every other language ecosystem. I would say, C makes it _possible_ to write code in such a way that it works on many different platforms and compilers, and/or can continue to work in decades to come with relatively little maintenance.
The latter isn't true for most other ecosystems, because to get anything done in them, you need to rely more on that language and its ecosystem -- specific language features, specific libraries. C isn't a glue code language. It's very good at letting you create your own thing.
To do it well though, requires a lot of care and expertise.
> more than for most other ecosystems, there are many different implementations with their own implementations and quirks.
Is that the reason? I thought it was primarily because C stupidly* makes a ton of platform-specific things more convenient than the platform-independent equivalent - the width of integer types, locales, etc., all vary by platform, and it's easy to accidentally depend on them.
* Maybe it wasn't stupid at the time when C was developed, but it's a bad choice now.
Integer widths are a problem, but hardly the biggest headache (unless it's a decade old preexisting project that was never designed with portability in mind). Fixed width integer types like int32_t have existed for decades. Even Rust has usize which is machine dependent, and pointers as well have to be machine dependent of course.
Much bigger problem is understanding the scope of dependencies. Dependencies can go bad and you need to update. A dependency might not be available on some new platform so you might have to replace with something else on that platform. To make the software portable, good modularity is required. This is to a large extent an aspect of software architecture. Even Rust can't magically make this happen, if you depend on 300 crates that's probably not a great place to be in either if you want to be portable and maintainable.
There is probably a point that C's flat namespace is a major contributor to badly designed software, because people aren't aware of the dependencies they're mixing all the time. Also C encourages transitive includes, leaking implementation details to the user instead of just to the compiler. (But btw. I find C++ and Rust namespace to be unergonomic syntax-wise, and what's needed is not actually namespaces but control over visibility).
* Programmers are lazy, they're going to type int more often than they should
* All these fancy modern "sized" types are actually just aliases for C's built-in types like int chosen to match the sizes people want...
* ...but C's integer promotion and silent rules about conversions work only on "real" C types you were trying hard not to think about and all the rules for those types, including types you never mentioned but were using because of promotion, apply to your program. You can write code which looks like it's all unsigned, but oops, under the hood a promotion means a signed integer came into existence, then it overflowed, now you have Undefined Behaviour.
I am interested to hear what you don't like about the namespace rules, in Rust in particular.
True that integer promotion rules are defined in terms of the machine dependent unsized "real" integer types. That is indeed annoying. I guess I've found ways to cope. Good thing is, those promotions never cast down, they only widen types.
The point of namespaces is exactly to allow less qualified names, to allow spending less energy when creating new names, by having the compiler always add a qualification implicitly. However, that doesn't solve the problem with name collisions when reading or grepping the source code -- it actually makes it much worse. And avoiding those name collisions, i.e. what's in the source literally, what the programmer reads, and what less-than-perfect tools see, is at least as important as avoiding "semantic" name collisions and being able to compile the code.
Namespaces not only encourage many more literal collisions -- now a single name can be available in various shortenings, and even under different aliases, which also makes it hard to tell that two symbols refer to the same thing!
I've found that the only way to get names right is to find a good scheme, to find meaningful good prefixes, suffixes, and abbreviations, and to come up with a system to create new names more mechanically but still keep them as short and readable as possible.
I think I mostly agree with you about what namespaces should do for us, but somehow I end up liking Rust's choices and you didn't. Interesting.
I like that core::cmp::Ordering and core::sync::atomic::Ordering exist because in their contexts I think these types should both be named Ordering and so it would be annoying if they were named Ordering and AtomicOrdering or something. If I'm writing code which does tricky atomic operations I can bring in core::sync::atomic::Ordering and now Ordering means that one, whereas if I'm writing code about whether this Goose is before or after that Goose, I ask for core::cmp::Ordering to get the relevant Ordering.
And if it's unclear in context, I can write core::sync::atomic::Ordering and everybody knows which one I meant.
I don't usually use an IDE but I believe it's usual in an IDE to tell you the full names of such things and even to help you import relevant stuff based on completions.
I find both of these names too long and the colons are noisy. The fact that I can refer to them using different qualifications makes me uneasy. For both we could find better names, e.g. MemoryOrder is already an established term.
I've realized that naming is an art that is difficult to master but makes a huge difference when gotten right. It consists of not just combining names into compounds but also finding new shorter names that are ergonomic and distinguishable. For example, I've replaced the compound "NodeIntersectTest" in my current project with simply "Probe", which is much easier to read and write, easy to distinguish from other terms after learning its meaning once (it is a central concept to my app), and also perfect for log messages and stats where brevity matters a lot.
A two letter prefix for all identifiers in a project or company codebase is a good idea, but I try to avoid
hierarchies and finer granularity, and generally to be overspecific, in order to keep things easier to change later.
It's a slow generic data structure with an unintuitive API. You can use it for leetcode or for CRUD. For anything more demanding it's horrifically bloated and bad. std::string too. Whenever you see STL datatypes like even string and map, you have to deal with RAII, implicit allocations, weird operator syntax, unexpected mutation (invalidation) and so on.
(Spporting or even encouraging destructive mutation, and by this I mean not incrementing counters or anything harmless but allowing iterator invalidations and crashes, are also why std::vector is bad in my opinion, these are idiomatic APIs for 90s and 2000s programming, which we should know better to avoid in 2026).
> For anything more demanding it's horrifically bloated and bad
C++ containers are designed for average demands. If you need something more specific, you can always use an alternative implementation. And it's better than messing with macros in pure C.
> you have to deal with RAII
What's problematic with it?
> implicit allocations
Allocations aren't implicit. It's usually clear from the documentation where allocation takes place (like in concatenating strings or copying strings).
> unexpected mutation (invalidation)
It's not the case with standard library containers. Mutating methods aren't const-qualified, so that it's clear where mutation can take place. And in C++ it's strictly recommended to mark as const everything in regular user code which shouldn't be mutated.
> C++ containers are designed for average demands. If you need something more specific, you can always use an alternative implementation.
As someone who has been programming exclusively in C/C++ for a long time -- the biggest selling point for these languages in 2026 is exactly being suited to doing something specific, not generic.
> Allocations aren't implicit
std::map m;
m[1] = 42; // implicit alloc
auto m2 = m; // implicit too
> in C++ it's strictly recommended to mark as const everything in regular user code which shouldn't be mutated.
Theory and practice. Making everything const correct is too painful in practice, often impossible. It's a trap for bean counters that can't focus on getting some actual useful work done. I'm being harsh to my own past self here. Counter question: why include allocation mechanics with some existing data that never gets changed? Why should we have to add much more boilerplate to get the simpler thing?
operator[] of associative containers is mutating and may allocate. It's mentioned in the documentation. And there is no operator[] overloading for const instances, so that you can't trigger an allocation by just reading elements from it.
> auto m2 = m; // implicit too
Taking a copy requires making an allocation. Do you expect some other behavior in such case?
> Making everything const correct is too painful in practice, often impossible
Maybe you are dealing with some legacy codebase (from 90s)? I have worked with multiple codebases in past 10 years or so and keeping things which shouldn't be mutated const wasn't a problem at all. All the code was written using such approach.
> why include allocation mechanics with some existing data that never gets changed
I don't think I fully understand this question. What never gets changed? In your example you are mutating a container and taking a copy of it (which can be changed later).
> auto m2 = m; // implicit too
Taking a copy requires making an allocation.
C++ makes it “look nice” at the cost of clarity. But not consistently. Overall, despite addition of syntax sugar in various places to make things look smooth, we arguably have “a lot of syntax” (“syntax castles”) in C++, as a result of embracing a lot of complexity. C, OTOH, avoids both sugar and castles.
No, just saying this stuff is implicit and thus hard to read. More so the allocation on indexing assignment.
and keeping things which shouldn't be mutated const wasn't a problem at all.
> and keeping things which shouldn't be mutated const wasn't a problem at all.
As soon as datastructures become more complicated and more interconnected, it's not clear anymore what const should even mean (issues are similar as with deep vs shallow copies, how do you even draw the lines, where are the objects?).
And the major philosophical flaw in the const vs. non-const distinction is that to make a strict separation you have to move everything mutable into constructor calls (for the most part, not getting more into the weeds of C++). Which is very awkward, it's a real tradeoff you need to be aware of. I realized only after playing these games for a long time what a waste of time it is and how much complexity it creates.
Great that setjmp/longjmp is explicit, and that in C there are even more liberties to cast away const than in C++, while some surprises in embedded, when the compiler decides const could have been placed into read only memory.
Placing data in .rodata (read only memory) is an actual useful application of const keyword. Not specific to embedded. Arguably there shouldn't have been multiple slightly different meanings of the keyword like that.
I don't know why you are now jumping to setjmp/longjmp, trying to make a point that somehow C is the worst programming languages ever, when all I am trying to argue is for sane programming practices, hinting that more complex features (line especially found in newer languages) are not solutions but part of the complexity problem.
You must be very experienced to be so judgemental! But maybe ask the many billions of voxels that get tested per second on my multithreaded and SIMD'ed cutting simulation.
C++ is the best language to write multi-architecture SIMD without relying on compiler magics like autovectorization. It gives you enough tools to define zero-cost abstractions to make SIMD nice.
Yeah... There are libraries like Google Highway but that seems to me like a bulky software abstraction layer even if it is a so-called "zero cost" abstraction. Maybe appropriate in some cases. In my case, I've implemented a couple of lines for AVX2 and AVX512, maybe around 100 lines for the most perf sensitive sections currently, not a big deal. The actual work is in the understanding of the problem and of the phyiscal hardware, and in the software architecture and data layout to even enable vectorized stream processing in the first place. I'm not concerned writing a couple of perf sensitive lines against 2-3 most important concrete vector ISAs. I also fancy adding a GPU backend later (requires D3D12/SM6.0 API level), Highway couldn't help with that.
RAII is terrible for memory resources - it's ok for things like locks I guess.
Rarely do I want to couple initialization with allocation, I want bulk allocations/deallocations (and I want precise control over when that happens) and reset()-like functions so I can easily reuse memory.
It couples things and complicates things. Which creates friction against when refactoring a software architecture, making it harder to scale it up.
Fully agree, for locks or other simple begin/end pairs it doesn't create the same kind of architectural damage. But I don't use it even there, because RAII fundamentally doesn't play well with manual cleanup code. It's kind of an all-in game, if you use some RAII there are strong forces (whenever you need to get the "nesting" right) to convert all remaining manual begin/end type code to types that define that functionality out-of-line in the destructor/destructor, leading to fragmentation.
At least is there in the standard library, one doesn't need to invent one from scratch in every single project, or reach out to the tools floppy while not telling the employer due to code ownership.
No need to invent when you already know how it works.
I could re-type all the essential stuff from std::string that I've ever used myself within 10 minutes, no problem, without looking up external sources, including copy & move semantics and whatnot. But I'm deliberately not doing it. Because I don't like this "invention".
Exactly, and a simple usable string type is just a struct MyString { char *buf; size_t len; }; away. Actual magic is in how you use it, where you allocate it, how you integrate allocation and formatting and logging and I/O... i.e. all the things that aren't solved by crufty complex std::string either.
The projects and people I follow do this all the time. Just like putting a size field near each regular non sentinel terminated array is useful. But if not doing it (maybe linux kernel in many cases), no big deal either, don't sweat the small stuff. String slices are small and rarely perf critical.
I for one love to watch and listen to him, not for the fact that I find myself agreeing to so much of what he says, but because he is able to express things in very insightful ways. And yes, I'm a sucker for that "no bullshit" attitude.
reply