A common use case for cstdio/iostreams/std::format is logging. It's not at all uncommon to have many, many log statements that are rarely executed because they're mainly for debugging and therefore disabled most of the time. There you go, lots of rarely used and even more rarely 'tested' formatting constructs.
I don't want things to start blowing up just because I enabled some logging, so I'm going to do what I can to let the compiler find as many problems as possible at compile time.
Yeah, it looks like you did a lot of guesswork in that comment, and a lot of those guesses were inaccurate. Not really trying to be hostile here, but you did acknowledge that you were unfamiliar with std::format.
The part that fmtlib / std::format has, which is printf-like, is the idea of having a format string and arguments, rather than having a bunch of separate, piecemeal strings.
// Old printf code, works ok for most people
std::printf("failed to clone %s from %s", target, src);
// <iostream>
std::cout << "failed to clone " << target << " from " << src;
// New std::format / fmtlib
std::print("failed to clone {} from {}", target, src);
You can see that you don't need to remember what kind of format specifier you need. This is C++, and that kind of problem is solved with overloading.
The std::print interface can work equally well with FILE or std::ofstream, or whatever you want. This is C++, and so you can just use a templated output iterator—or one of the overloads that creates one automatically.
There are a lot of problems with <iostream>. I think it’s telling that lots of languages have copied printf, but nobody (or almost nobody) thought <iostream> was good enough to copy. There are just too many serious design flaws with <iostream>. It would be one thing if <iostream> were just annoying to use, but it poses problems for localization, thread-safety, accidental misuse through its statefulness, and its operator overloading syntax is bad.
> I'm not saying it's impossible to write a bad printf line and never test it, only to have it fail years later in production. It's absolutely possible and it has happened to me. Lessons learned.
A modern compile time type checked formatter would have prevented this mistake, you are deliberately choosing to use poor tools and calling this "pragmatism" because it sounds better than admitting you're bad at this and you don't even want to improve.
In fact C++ even shipped a pair of functions here. There's a compile time type checked formatter std::format, which almost everybody should use almost always (and which is what std::println calls), and there's also a runtime type checked formatter std::vformat, for those few cases where you absolutely can't know the format string until the last moment. That is a hell of a thing, if you need one of those I have to admit nobody else has one today with equal ergonomics.
> Why is there an input stream going into a function?
The function is constructing a temporary object and reading into that object. The object includes a reference to a named tp object, and the temporary parser object is parsing from the input stream, which writes the data into the tp object.
> but... I still don't understand all of the technical reasons behind that decision.
Because he wants to support input from an istream, and inputting from a istream necessarily means overloading '>>'. That's just how things are done in C++; if you want to support reading from or writing to an istream/ostream, you overload the '>>' and '<<' operators.
It's a trade off; he could have designed it so that the constructor of tp simply accepts the format string and the istream as parameters. But that has the downside of not looking like idiomatic istream code.
I personally think that the way iostreams are implemented in C++ was a mistake. (because I don't like the way idiomatic istream looks, and I think friend functions are generally smelly.) Unfortunately, it's something like 35 years too late to correct it. (I'm aware that C++ is only 33 years old; iostream.h and cout << "blah" << endl; and cin >> foo; preexist C++) But here's the thing: it's badly designed iostreams that make the linked code confusing, not operator overloading.
> The second point (how to print an integer) is about something way less easily discoverable than in the previous example. You need to learn about how to print, then about `fmt` and from there the only authoritative place that contains information about format specifiers is the doc comment of `std.fmt.format`.
I don't know if ZSF can guide this or if we can incept it into Andy's head, but if there were any coordinated plan to "solidify certain parts of the stdlib sooner than other parts" the std.fmt.format would be high on my list. And document it. Hell. document it, even if it changes around all the time. I'm often forgetting how that beautiful bastard works and having to jump to the comment in the stdlib is kind of annoying.
> ...what is the use case for a program that operates on arbitrary user defined types?
That's not what's going on. Neither C nor C++ really permits that.
We're just talking about the relative merits of using printf and friends vs. using iostream to format data for the console or disk.
In either case, you have to write the code for your class that massages the class's internals correctly, but the question is, do you write to the ostream extraction and istream insertion API, or do you write to the substantially less well defined printf-and-friends API?
> The thing though is that these checks are NOT free and the overhead is not justified for all use cases so forcing them on everyone is not appropriate for C++.
Well, that's why they should be a flag. The question is whether it should be enabled by default or not.
> I honestly don't know how C++ programmers typically handle this situation.
The verbose one. A few extra lines rarely matters. I think the number of times this has come up in my code base is very very small, maybe a few dozen extra lines across hundreds of thousands.
This is clearly disingenuous. C's string formatting is a completely different thing from that Rust snippet or your snide remark about std::format.
I'd rather use printf than cout, I think iostream is clunky, but printf is definitely a footgun & a fairly common source of bugs itself. So much so that compilers had to add support for specifically recognizing printf() calls & validating the inputs.
Also don't forget C still doesn't have standard placeholders for sized types. Gotta use that derpy PRId64 & friends which makes printf() almost as ugly looking as iostreams.
> I spend an annoying amount of time trying to compare/add/multiply... different types in C++ like time, durations, dates, ints, floats in my day job.
Multiplying ints and floats "just works" in C (although you have to mind overflow, but that's not a type issues).
Other examples don't make a lot of sense. E.g. why would you need to multiply time by duration, or compare one with the other? They're different quantities, reflected as such in the type system. That it won't let you do something like that is the whole point - it would be a bug if it did (and if not, then the data types were chosen wrongly).
OTOH, adding a duration to a date is semantically meaningful - which is why std::chrono has operator+ overloaded for various combinations of time_point and duration.
>If I had "using namespace std;" then all of my code is suddenly broken.
If this happened and if you included that specific header, you would get a simple compiler error due to ambiguous methods. Then you can simply add a namespace somewhere and move on. I know sometimes this can involve a lot of changes (if we're talking about other `using namespace ...` usage) but in general you're not gonna have problems.
If you're really worried about this, you can move the statement closer to where you need it. This is very helpful for dense code that you normally want to be verbose.
> C Printf never was, never could be and never will be a suitable way to output data from C++. Now excuse me while I go through the list of thousands of predefined format macros to find out which I need to use to output a uint_fast16_t without making the compiler vomit nonsense.
printf("%d\n", (int) myfast16_t);
Not that terrible for a type that I've never used, nor seen used.
See, I don't believe that's an improvement. Having used printf in C, I was relieved to be able to "redirect" whatever to a stream, and not care about whether it should be "%d" or "%02f" or even if it was a struct/class.
On top of this, treating files as streams, strings as streams, or even extending streams to make a tee-stream[0] all seem clunkier to me with a printf like system.
Maybe fmt fixes these problems, I don't know. But I feel a lot of people don't like iostreams because they have some form of Stockholm syndrome with printf.
A common use case for cstdio/iostreams/std::format is logging. It's not at all uncommon to have many, many log statements that are rarely executed because they're mainly for debugging and therefore disabled most of the time. There you go, lots of rarely used and even more rarely 'tested' formatting constructs.
I don't want things to start blowing up just because I enabled some logging, so I'm going to do what I can to let the compiler find as many problems as possible at compile time.
reply