The entire reason for integer under/overflow to be undefined is to enable compiler optimisations. If you're going to be using unsigned anyway, we might as well drop the undefined behaviour from the standard and just say it's machine-defined. That should honestly be the correct choice. If a loop is hot enough to benefit from those optimisations, you can easily rewrite it in a form that makes the compiler assume overflow won't happen. Either using current syntax with unreachable(), or we can add a runtime_assume(expr) expression that signals to the compiler that it can assume expr is true. Though for full safety I would prefer using if(likely(expr)) {fast code} else {panic or return error}.

As an aside, unsigned does not save you from undefined behaviour. When sizeof(short)==2 and sizeof(int)==4 (e.g. x86, x64, arm32, arm64), then multiplying two unsigned short values happens by upcasting them to ints (see integer promotion rules), which can overflow the int.

My personal opinion is that along with making signed overflow defined, unsigned integers should be entirely removed as a type and there should instead be separate signed vs unsigned operators, because at the processor level there is no difference between the two, and there hasn't been a good case to separate them at the hardware level for the last ~half century. Basically, do what Java does with some syntax like unsigned{expr} which forces all integers inside expression to be treated as unsigned. Unsigned literals can stay, but they will be bitcast to signed equivalents if used outside unsigned context.