This is the eternal selection pressure that slows new C++ adoption.
The kinds of places still waiting C++ aren’t usually the ones that put much emphasis on using a compiler from the past decade.
Java 8 and C++98 will be here forever lol
Is it in major compilers yet? Last I checked for MSVC it was behind a "latest" compiler flag (not C++20). I've been vendoring the fmt library for awhile now.
From GCC 13, and clang 17. (2023).
Unfortunately, MSVC, always lags and fails to implement some things.
[deleted]
std::print / std::format also bloat the binary size, which is a consideration for some platforms (eg WASM).
char buffer[64];
String_Buffer buf = {str, sizeof str};
Probably meant the "buffer" to be "str" here.
Clearly yes.
BTW, I don't see a benefit to use a non-owning String_Buffer over std::string (or std::string_view) in this context.
The subtext is a resource constrained system where std::format is considered too heavyweight. In that scenario, explicit non-automatic memory management is a benefit. It could still leverage std::string_view and be agnostic on the topic.
I have recently started re-implementing parts of the standard library myself just to improve compile times (and I did - massively!), but I purposely kept {fmt} around, because I think it's a great library and I thought it would be as fast to compile as you could possibly make it (it likely still is considering what it can do). Also because the dev put a lot of effort into optimizing compile times [1] and seems to be much smarter than me. So I made benchmark to prove you wrong and show you it's not that easy. But it turns out formatting a couple of numbers and strings is much faster with your basic formatting library [2] [3].
Comparing using `hyperfine --warmup 3 "g++ FILE.cpp"` (and some flags for fmt) I get 72ms vs 198ms. So I changed my mind and might take a crack at replacing {fmt} as well. Cool stuff! Thank you.
[1] https://vitaut.net/posts/2024/faster-cpp-compile-times/
[2] https://godbolt.org/z/3YaovhrjP bench-fmt.cpp
[3] https://godbolt.org/z/qMfM39P3q bench-rikifmt.cpp
rikifmt only supports the default formatting which is somewhat limited and in the current implementation cannot even format std::string or FP numbers without data loss. Once more functionality is added the variadic template approach will quickly become the limiting factor both for build speed and binary size. But for toy examples you can definitely do better.
I'm lost at the first line of code:
char buffer[64];
And then it's not used anywhere!
I was curious about the "Why not printf" section, but I found code I don't understand there, too. For example this admittedly non-working snippet is cited as idiomatic:
char str[4] = {0};
int cursor = 0;
cursor += snprintf(str, sizeof str, "hello ");
cursor += snprintf(str, sizeof str, "world!");
Of corse this doesn't work (if the intent was to assemble the "hello world!" string, of which I'm not entirely sure), but not for the reason stated in TFA. You need to actually use cursor, not merely set it! :)
Nice. I think most people have tried doing something like this in C++ at some point.
One issue that I had is that printing floating-point values really needs the ability for the user to specify the precision and format. It's actually absurd that `std::to_string(double)` does not allow this.
Also, I believe `std::to_chars(double)` uses a fast algorithm and allows writing directly into a buffer.
If you're willing to use one measly little macro - solely to smuggle the format string in a constexpr manner - instead of insisting on using templates everywhere, you can use a printf wrapper with essentially 0 compile-time overhead. And the only runtime overhead is if you have to copy a `string_view` back into a `string` to add the `NUL`-terminator.
You do still need templates for the arguments (unless you're willing to resort to nasty preprocessor hackery, which would be needed if doing this in C - hmm, are the lifetime-of-temporary rules different too?), but it's pretty easy to just do:
my_asprintf_or_whatever(to_borrowed_primitive(to_owning_primitive(arg))...)
where `to_owning_primitive` is the ADL'ed function you implement for every type you want to print, and `to_borrowed_primitive` probably only needs to be implemented for each string type (though I did find it also useful for wrapped integers of unknown size/rank, such as `time_t`).
Pretty neat and a very nice walkthrough of the code.
For localization you might want numbered holes which makes it way more complicated.
You can detect if the backing buffer is too short, but can you detect other errors? Like having different numbers of holes and arguments? I couldn’t find any discussion about this.
Next time can you to do Python's string interpolation? ;-)
It's much more pleasant to read (when properly used).
Love the method name uhm
bool next_hole
I much prefer string interpolation like
$"i={i}"