> But what use case do you have for printf that can't be implemented with println!/format! and friends?
I'm guessing they mean that the format! machinery is rather complex and brings in a lot of code. That it's also somewhat slow is a recurring concern .
Have none of the embedded folks created a specialised version of format! which is not generic, and basically only supports the types and styles which c's printf supports?
> Will you ever add / have you considered adding sane formatting options for fixed length variables in printf? Say %u32 or %s64 ?
I'm not certain about the historical answer to this, but I do know that we're currently considering a proposal to introduce an exact bit-width integer type '_ExtInt(N)' to the language, and how to handle format specifiers for it is part of those discussions, so we are considering some changes in this area.
> Have you considered adding access to structure members by index or by string name? Have you considered dynamic structures?
I don't recall seeing any such proposals. I'm not familiar with the term "dynamic structures", what do you have in mind there?
>and how to handle format specifiers for it is part of those discussions, so we are considering some changes in this area.
Please, please, please pick short and descriptive format specifiers, like %[suf]\d+, ie
s64 v=somenumber;
printf("%s64\n", v);
_ExtInt(N) and PRIx64 etc look absolutely horrid. u?int\d+_t are also really bad, it would be great to have just [suf]\d+ as types, where \d+ is 8, 16, 32, 64 for [us] and 32 and 64 for f.
>what do you have in mind there?
Say like VLAs but structures with members that are dynamically defined and used.
> ...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?
> 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.
> Are we seriously talking about nested pointers, alignment and CPU registers before we've even introduced printf?
No, printf is introduced in the very first hello world example. But the concept of format strings is not explained, nor are variadic functions, and no documentation for printf is linked.
Also, the example
#define MY_TITLE "Hello world"
printf("This is: %n", MY_TITLE);
is really quite broken. %n needs and int* argument and writes the number of characters written so far to it. A string constant is not only of the wrong type, even more importantly it is not writable (though not const).
Of course a modern compiler with -Wall would catch this, but the tutorial never mentions any flags you might want to pass to your compiler...
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.
> 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.
> It's also ugly to up-cast everything to the largest potential size whenever you use format strings.
I'm not sure that is true. It accurately captures the reality that you don't know the size of the type, but that you have determined what the maximum size can be and hopefully made considerations for it.
I should think there isn't even necessarily a performance cost, as it wouldn't be hard to trick out a compiler to recognize what was going on and optimize accordingly.
> What type would you use if you wanted to print uint128_t? %llld ?
IIRC, there is no standard portable format string length modifier for 128-bits (I think some platforms used %q for it, but that's definitely not portable), so literally nothing. Format strings suck.
> Finally, I think rejecting a standard C header file because it is "ugly" and coming up with your own solution is unnecessarily fragmenting things, especially when it isn't clearly better (IMO it is clearly worse).
Note that as the presentation points out, the better thing to do is whatever is going to easily adopted. In this case, where people are already using format strings, and already working with a time_t that might be only 32-bits wide, this might actually be that solution.
What consistent mechanism do you use to printf an instance of an arbitrary user-defined type?
With ostream, you implement
ostream& operator<<(ostream&, const Class&)
and you're done.
with printf, you have to have a convention and remember to stick to it. (Do I call .toString(), .stringize(), .getString()... ??)
For one-off things, printf is great. You take it from me when you pry it from my goddamn cold, dead, hands. However, for real work io*stream are rather nice.
> You might think, but who in their right mind would redefine printf()?
One cannot discount those in their wrong mind either.
I had a header dedicated to #undef ing things ruby.h redirected so I could include it in C++ contexts without breaking the SC++L. While I don't see printf on the list, read, write, close, fclose, sleep, and many more are.
> 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.
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.
> Isn't Int -> String -> String a shorthand for Int -> (String -> String)?
Yes. Though I find it more convenient think of it as a function with multiple arguments and not a function that returns another function.
> Thus I would have assumed the first argument to the result of sprintf "%s %d" would be an integer, then a string and the final result would be a string.
Why assume that? (Not rhetorical.) Wouldn't that be like if in C, you had to write the arguments in reverse order of the format string, like this?
> "How do you portably printf() an integer type that you don’t (and can’t) know the size of"
"Portably" across what? You need to narrow down the problem space and better define it.
If it's portably across a myriad of processor architectures, hardware profiles and operating systems then you would need to take the intermediate representation route (like Java). There's very little else that can be done in the face infinite, non-compatible ABIs at every layer.
You will have to define "good". My string library[1][2] is "good" for me because:
1. It's compatible with all the usual string functions (doesn't define a new type `string_t` or similar, uses existing `char *`).
2. It does what I want: a) Works on multiple strings so repeated operations are easy, and b) Allocates as necessary so that the caller only has to free, and not calculate how much memory is needed beforehand.
The combination of the above means that many common string operations that I want to do in my programs are both easy to do and easy to visually inspect for correctness in the caller.
Others will say that this is not good, because it still uses and exposes `char *`.
I'm guessing they mean that the format! machinery is rather complex and brings in a lot of code. That it's also somewhat slow is a recurring concern .
Have none of the embedded folks created a specialised version of format! which is not generic, and basically only supports the types and styles which c's printf supports?
edit: there's ufmt though it seems somewhat inactive and I've no idea how good it is: https://github.com/japaric/ufmt
> Optimized for binary size and speed (rather than for compilation time)
> No dynamic dispatch in generated code
reply