I was too. Looking at [0], I'm guessing it's meant to bring these benefits over old-school "printf":
- Leverages the type system to avoid mismatches between the format string and the arguments.
- Adds Python- / Rust-style support for position-based substitutions, e.g. "{1}".
- Like Rust (I think; I'm still learning Rust) you can specify per-type formatters.
But I'm still confused about:
- How this relates to the standard library's iostream system. It seems pretty redundant.
- Why "println" exists.
print/println are based on the fmt library (https://github.com/fmtlib/fmt), which had it's first release in 2015, so it's roughly as old as Rust afaik. It's mainly inspired by Python formatting.
Having per-type formatters is just a logical conclusion of having type-safe formatting.
iostreams are for all kinds of io (which includes stdin/stdout), while fmt is entirely dedicated to formatting strings. Those things are related, but not the same. cout and cerr and such will probably be entirely superseeded by print/println (I hope), but it doesn't make iostreams generally redundant.
println adds a newline and you want to be able to choose, so there is print and println.
In C++20 if you wanted to print using std::format style formating (or it's variant) your options where:
```
std::cout << std::format("{}{}", arg1, arg2);// not the best syntax but less bad than everything else, slightly inefficient due to temp string
std::string tmp = std::format("{} {}", arg1, arg2); fwrite(stdout, tmp.c_str(), tmp.length()); // more ugly, and still uses temporary string
std::format_to(std::ostream_iterator<char>(std::cout), "{} {}", arg1, arg2); // avoids temp string, but what kind of monstrosity is this
```
But doesn't the std::format style formatting make the formatting part of ostream redundant -> it somewhat does. I guess that's why there are 3 types of print overloads:
* accepting ostream as first argument
* accepting c style "FILE*" as first output argument
* no output stream argument, implicitly outputting to stdout
One potential consideration to use ostream based versions instead of FILE* ones even though the formatting part is somewhat redundant, is RAII based resource management. If you want to use FILE* based versions, it means that you have to either remember manually close the File* handle which just like manual new/delete or malloc/free calls is something modern C++ tries to move away, or you have to create your own RAII wrappers around c style FILE* handles.
An alternative would have been introducing new kind of output stream API which only concerns with outputing buffered stream of bytes and skips the formatting part of ostream, but that would have introduced different kind of redundancy -> having 3 kinds of API for opening and writing to file. Allowing to pass ostream to print, while discouraging use of << operator parts of it seems like a simpler solution.
One more concern is how using the version of print which outputs to stdout without taking output File or ostream handle interacts with any buffering done by previous printf,and std::cout APIs and also synchronization with input streams. The same problem already existed before for interactions between printf and std::cout. For the most part if you don't mix them it doesn't matter too much, but if you care the standard defines how exactly they are synchronized. The new print probably either adds third case to this or is defined as being equivalent to one of the previous two.
After looking reading docs a bit more, seems like std::basic_streambuf/std::file_buf did exist. The new print API, might have used that as abstraction around output streams without some of the ostream formatting baggage. I have only seen them mentioned as implementation details of ostream stuff, never seen anyone use them directly.
There was also std::format_to in c++20 which avoid the temporary string std::format returns. I guess they could have extended that with additional overloads so that it can function more similar to std::print. But if they need to define new functions might as well call them something more familar to people coming from other languages like print, instead of having format_to(stdout, "formatstring", arg1, arg2);. Currently std::format_to outputs to output iterator.
So to sumarize why print exists: - combine std::format introduced by C++20 with outputting somewhere with cleaner syntax compared to doing it manually.
- sane looking hello world for beginner and people coming from other programming languages, this is probably also why println exists
- (maybe) avoid some heap allocations that temporary string returned by std::format would cause.
- avoid confusion of mixing two formatting approaches that `std::cout<<std::format()` implies
- avoid C style manual resource management that would be required for doing `File\* f=fopen("");auto tmp=std::format();write(f, tmp.c_str())`print("{:#04x}\n", 0xbeef);
- or -
cout << std::setw(4) << std::setfill('0') << std::hex << 0xbeef << std::endl;
I kind of wonder why anyone thought iostreams would be a good idea to begin with. I don't think anyone but C++ ever created a comparable interface.
1. Type safe string formatting. You'd get a compile error if the value you tried to write didn't support it.
2. User-defined formatting support for user defined types. You could make instances of your own class support being written directly to a stream. Sort of like how some other languages let you override `toString()`.
3. No runtime dispatch for #2. Unlike Java, C#, etc. the user-defined formatting code for your type is called statically with no overhead of virtual dispatch.
Using operator overloading to achieve all of those was really clever and led to a very powerful, efficient, and flexible system. It's also just unfortunately pretty verbose and kind of weird.
IDK, it'll probably make more sense in another 15 years as we clear away the cruft of all the things that tried to bring "cloud native" paradigms into a space where they didn't really fit...
The big thing is that they wanted type safe IO. Not like printf where you can print an integer with %s and the compiler won't have a problem with that, and it will crash.
Reusing bit shift operators for IO is quite clever actually. If you have operator overloading, you have type safe IO for free. Remember C++ came out in the 80s as a superset of C, these technical considerations mattered. std::println doesn't look like much, but it actually involves quite significant metaprogramming magic to work as intended, which is why it took so long to appear.
It's a miserable trap. Operators should do something in particular, because of the Principle of Least Surprise. The reader who sees A + B should be assured we're adding A and B together, maybe they're matrices, or 3D volumes, or something quite different, but they must be things you'd add together or else that's not a sensible operation for the + operator.
When you don't obey this requirement, the precedence will bite you as, as it does for streams. Because you forgot, these operations still get bit shift precedence even though you're thinking of this as format streaming it's just a bit shift and happens when you'd do a bit shift...
Streams looks like something dumb you'd do to show off your new operator overloading feature, because that is in fact what it is. It should have existed like Mara's whichever_compiles! macro for Rust - as a live fire warning, "Ha, this is possible but for God's sake never use it" - but instead it was adopted for the C++ standard library.
Iostream predates parameter packs, which is why they use << for a variadic API.
Just like Pascal/Modula-2, except 30 years later :-D
Now if they could fix the CaSe SeNSiTivity bug, get rid of macros, get some better strings, and maybe use @ to get an address, and ^ to point to things.... they'd be on to something.
Especially getting rid of macros, I hate them.
Most C code looks like line noise, maybe they could then use something like Begin/End to denote blocks instead of abusing comment markers {} ;-)
Now I still miss my favorite language, FP...