Thanks for fleshing this out. Before I address the larger point, lemme just mention one thing regarding the Your program increments dst past the end, goes "whoops", then goes back and writes in the NUL byte bit: it's not really going "whoops", it's just (a) copying everything except the null terminator, then (b) tacking on the null-terminator.

The reason I bring this up is that your characterization actually misses the single most important aspect of that code. Notice that in your own implementation, it's not obvious in the last branch whether the buffer will end up null-terminated or not. To infer that, the reader will have to jump around the code while performing the following mental gymnastics to execute the program backwards: (a) !!len == true in the final branch, (b) therefore --len > 0 earlier, (c) therefore that condition was inactive in the loop, (d) therefore the loop terminated because of the other condition (i.e. because dst had a NUL written into it), (e) therefore therefore dst is NUL-terminated because it is not advanced afterward. I'm not saying people can't do this, but it requires people to focus on the code and concentrate to figure it out. It's essentially a puzzle they need to solve, and that's before maintenance. There's no indication to the maintainer that this logic is implicitly there, so you're really just hoping that every step of this multi-step chain stays intact during maintenance. Contrast this with the index-based solution, where it is blatantly obvious that the returned string is NUL-terminated... the return value is &dst[i] and the very line before it literally tells you dst[i - 1] = '\0' when i > 0! So you can verify the most important property of that function by simple inspection.

So now that I have that out of the way, regarding your larger point: what you're saying is essentially "this is a composition of well-understood primitives", i.e. the primitives are

while (--len)

and

while (*dst++ = *src++)

and your brain "parses" them as the high-level operations they are, and then simply reads them as compositions of those high-level operations, rather than as a bunch of pointer manipulation operation.

I guess it explains why you wrote it this way (thanks for that). What I'm saying is that when you force yourself to combine primitives like this, the solution ends up being more complicated than it needs to be.

If you still don't see this the same way, imagine what the logical continuation of what you're saying would be for n >= 2 primitives:

while (--len && (*dst++ = *src++) && *++ptr && (*foo-- = *bar--) && ...) ;

Surely at this point it's obvious that the whole is more complicated than the sum of its parts, right? Maybe one way to see it is that the composition of these n primitives doesn't just have n termination conditions; it has an exponential number (at least 2^n) of them. That's objectively exponentially worse. The only way you (or or your future reader) can dismiss differences in each of those states is by actually spending time analyzing the structure of the problem beforehand; it's by no means a given for an arbitrary problem. And n = 2 isn't exempt from this complexity growth.

Stepping back a bit, and this is a little bit tongue in cheek, but I'm reminded of a joke that seems kind of relevant... it's been years, but if I recall, it went something like this:

A mathematician and an engineer are presented with a problem to solve. Each of them is separately brought into a kitchen, where they see a curtain catching fire. They're asked to put it out. They think for a while, frantically looking around for something useful, and suddenly see there's a pot of water boiling on the stove. They look around, find some mittens, grab the pot, and dump the water on the curtain to put out the fire.

The next day, they're asked to put out a similar fire again, except this time the stove is off. The engineer sees the pot of water, starts looking for a mitten, but then notices the stove is off. So he just picks the pot up with his bare hands and dumps the water on the curtain. The mathematician instead just notices the stove is off, then turns it on to boil the water so he can finish the rest of the problem with the solution he figured out yesterday.

It's exaggerated, but I think a similar idea applies here. Essentially, you don't want to treat everything like a nail just because you have a hammer. Even in a hypothetical world where we could assume the primitives are perfect in isolation and your composition is 100% guaranteed to be bug-free code, this kind of "have idioms -> will use" approach to writing software would merely be guaranteed to give correct code with an upper bound on the required complexity. It would by no means guarantee the final solution is anywhere as simple or readable as it could or should be for that problem, and in this case, IMHO it just isn't.