On Mon, Jun 23, 2025 at 9:36 AM Douglas McIlroy
<douglas.mcilroy(a)dartmouth.edu> wrote:
Overloading
bit shifting operators to implement stream I/O was a repellent choice
Without commenting on this judgement, I can tell how the convention
came about in a casual conversation.
Thank you for this interesting historical note, Doug.
Personally, I've never found the `<<` or `>>` syntax troubling when
using C++ IO streams, and the type safety is a notable improvement
over printf/scanf et al.
What I _do_ find troubling is that the stream itself is a stateful,
mutable object, so to perform any sort of non-trivial formatting, one
has to inject special control messages into the stream using the same
syntax; these do compose (usually), but they're persistent, are overly
verbose, and cumbersome to use in practice. Here, the concision of
printf's "little language" of formatting verbs is superior, and far
more readable.
It seems obvious in retrospect that C++ would have been better off if
augmented with a small hierarchy of "formatting objects" that encoded
the various formatting directives, exposing a printf-like language, as
a constructor argument, along with the datum. The stream would consume
these for the purposes of formatting that specific datum, but then
discard them, as opposed to holding onto whatever state they induced.
Google eventually did something like this, which kinda-sorta made it
out in the `absl::StrFormat` library, but it took far too long (and
Google, for many years, explicitly forbade using iostreams in C++ code
for dubious reasons).
More recent languages have introduced happy mediums. I personally like
the way that Rust handles this; `println!` is a macro, and interprets
the format string at compile time, ensuring whatever it specifies
matches its arguments vis types, visibility, safety, etc. But at the
call site, it resembles the concision of printf via a small formatting
language.
- Dan C.
I was perhaps the earliest serious experimenter with
C++ overloading,
for a constructive solid geometry package in which arithmetic
operators were overloaded for matrices and vectors, and boolean
operators were overloaded for union and intersection of geometric
objects. I've forgotten how object subtraction (e.g. to drill a hole)
was represented. Sadly, the symbol vocabulary for overloading was
fixed, which meant that two vector products (dot and cross) vied for
one operator. Joe Condon put me onto an interesting analog of quotient
and remainder in 3D. The quotient. of two vectors, q=a/b is the ratio
of the projection of a onto b to b and the remainder r=a%b is the
component of a perpendicular to b, The usual quotient-remainder
formula holds::a = q*b + r.
Once, while discussing some detail of overloading, Bjarne remarked
that he'd like to find an attractive notation for stream IO, which
printf certainly is not. << and >> immediately popped into my mind.
They've been in the language ever since.
Equally casually, there was no discussion about the set of operator
symbols, so C++ did not come up with a convention for symbol syntax,
such as that of Haskell.
Doug