You're correct, of course, but back in 1990 people were way more excited about overgeneralizations, class hierarchies and what templates could possibly do. I mean, that was the time when using << and non-stacking stream manipulators seemed to be a good design for formatted output.
The C++ << stream operator is Bjarne's fault. It flies so very obviously in the face of the language's explicit instructions not to use overloading to give the same operator a different meaning, and the only reason it would be in the stdlib is that it's Bjarne's baby from the outset. He wrote the initial implementation and Bjarne's explanation for why this isn't a distinct operator appeals to Bjarne's own decision not to allow new operators in his language - basically "Because I said so".
I'm actually sympathetic to the stated goals of overloading, providing harmonic implementations of operators for custom types. If it makes sense to add a Goose to another Goose, a good language should let me use the + operator like on integers.
Everything beyond this I don't like, whether that's the (common) abuse of + to mean "concatenate" for strings, or the perverse use of << and >> in C++ streams. If your idea deserves an operator, give it a new operator, don't steal an existing one, that's my position.
Well, IIRC using the >>/<< operators for I/O was proposed by McIlroy (who also invented the Unix pipes), and Stroustrup liked it enough to adopt. And, well, I personally don't really mind it.
What I do mind is the stupid design of stream manipulators. First of all, their effect is not scoped until the end of the statement, so e.g.
out << std::hex << flags;
// ...
out << decimal_number;
will print the second number as hex as well which is normally not what you want. So you either need to manually set all the properties you care about all the time:
out << std::hex << std::setw(0) << flags;
// ...
out << std::dec << std::setw(0) << decimal_number;
or store the current flags of the stream before you do any output and restore them when you're done ― and both of those things kinda ruin the whole ergonomics which already were pretty bad compared to simple "printf("%x", flags); printf("%d", decimal_number)".
And the second, IMHO bigger, problem is that it's the std::ios_base that actually does all of the formatting and consults all of the formatting flags that are stored inside of it, and it's usually implemented in such a way that adding your own custom formatters/manipulators is basically impossible, not that you'd really want to do it anyway: making the custom formatter a wrapper around your data has minimal overhead and usually you can afford it. Adding a new formatting flag to the std::basic_iostream? Yeah, forget it.
And of course there is also the whole host of good()/bad()/fail()/eof() states and the "convert-to-bool" operator with very questionable semantics that for some stupid reason is not just an alias for good().
Stroustrup's description saya that "The idea of providing an output operator rather than a named output function was suggested by Doug McIlroy" but doesn't specify that Doug asked for this weird operator re-use which is the part I object to.
I agree that the manipulators are awful. I don't think you could "scope" the manipulators in the way you want without changing C++ because the compiler would need to tell the stream "Hey, I'm done now" and that's not how expressions work in C++, there's no "done" step when the expression's result isn't further used.
Because it has compile time reflection C++ 26 will probably make it practical for more C++ libraries to grant their types interesting formatting (with std::format and related modern stuff, not I/O Streams), and it will be educational to find out whether the result is a Cambrian explosion of interesting new ideas nobody can live without by 2040 or a sprawling disaster that is regretted in similar threads to this ten years from now.
This is a trick Rust doesn't get to do - unlike std::format, Rust's formatting rules are carved in stone, your type can be formatted only in pre-determined ways, if in a few years every C++ Goose type provides {:honk} and {:flap} which every Goose user intuitively understands then too bad the Rust Goose libraries can't do that, they're reduced to abusing existing features such as {:#} (Rust's alternate display flag) and hoping that's enough. To "fix" that the Rust language itself would need to revise the entire Rust formatter feature, whereas to do it in C++ just took writing some hairy constexpr code, expert mode for sure but hardly beyond possibility for the library maintainer.