A string formatting library in 65 lines of C++

(riki.house)

47 points | by PaulHoule 16 hours ago

9 comments

  • merlincorey 11 hours ago
    There's a section on "why not printf" which is Standard C, but I can't find any section on "why not std::format"[1] which is Standard C++ since C++20 and works on all major compilers today in 2025.

    They do mention "std::print"[2] from C++23 (which uses std::format) and compile times, but, they don't touch on "std::format" at all.

    See:

    [1] https://en.cppreference.com/w/cpp/utility/format/format.html

    [2] https://en.cppreference.com/w/cpp/io/print.html

    • hoten 10 hours ago
      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.
      • shakna 10 hours ago
        From GCC 13, and clang 17. (2023).

        Unfortunately, MSVC, always lags and fails to implement some things.

    • pton_xd 9 hours ago
      std::print / std::format also bloat the binary size, which is a consideration for some platforms (eg WASM).
  • teo_zero 2 hours ago
    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! :)
  • symmetricsaurus 3 hours ago
    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.

  • kevin_thibedeau 15 hours ago

      char buffer[64];
      String_Buffer buf = {str, sizeof str};
    
    Probably meant the "buffer" to be "str" here.
    • thw_9a83c 15 hours ago
      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.
      • kevin_thibedeau 15 hours ago
        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.
  • dpmdpm 15 hours ago
    I prefer https://github.com/rokudev/rostd/blob/main/doc/printx.adoc, but it does increase compile times (which OP was trying to avoid).
    • o11c 13 hours ago
      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`).
  • worstenbrood 15 hours ago
    Love the method name uhm bool next_hole
  • vjvjvjvjghv 14 hours ago
    I much prefer string interpolation like

    $"i={i}"

  • secondcoming 12 hours ago
    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.

  • cppisnice 15 hours ago
    How many CVEs?
    • speed_spread 14 hours ago
      Yes, true. But the probability of finding new CVEs from any 65 lines of non-obfuscated code diminishes rapidly. In many situations I'd rather use a short minimal fresh lib that I can review as if it was mine than a mature but overly feature-loaded one that may still have any number of pending gotchas in dark corners.
      • prerok 12 hours ago
        I must admit I was very much against the practice of NIH syndrome, but if it's that short I would prefer to write my own version instead of adding a dependency.

        In this day and age who knows when a dependency is hijacked :(

        • speed_spread 12 hours ago
          At 65 lines, if the license is right, you can just copy it like you would with a StackOverflow answer. In these situations I leave a comment on top saying where the code came from so it can be revisited later.