What does HackerNews think of fmt?

A modern formatting library

Language: C++

#5 in C++
I noticed std::format and std::print aren't even available with my pretty up-to-date compilers (testing Debian bookworm gcc/clang right now). There is only https://github.com/fmtlib/fmt but it doesn't seem prepackaged for me. Have you actually used std::format_to_n? Did you go through the trouble of downloading it or are you using C++ package managers?

I'm often getting the impression that these "you're a fool using these well-known but insecure libraries. Better use this new shiny thing because it's safe" discussions are a bit removed from reality.

But I'm asking in earnest. Please also check out my benchmark in the sibling thread where I compared stringstream with stdio/snprintf build performance. Would in fact love to compare std::format_to_n, but can't be arsed to put in more time to get it running right now.

> It haven't written C++ recently, but I recall that you can use ostringstream in place of ostream.

I don't know about those specifically right now, but in general these things have huge compile time costs and are also generally less ergonomic IMO. [EDIT: cobbled together a working version and added it to my test below, see Version 0].

> About "sigh of despair" and "sigh of relief": Are you expressing concern about the template function signature?

Yes. It's a mouthful, and I'm worried not only about static checks but about other things too -- like readability of errors, include & link dependencies, compile performance, amount of compiled code (which is minimal in case of snprintf/varargs)... I would need to check out std::format_to_n() as suggested by the sibling commenter.

And hey -- snprintf has been available for easily 30+ years ... while the and headers that people make such a fuss about, don't even seem available on gcc nor clang on my fully updated debian bookworm system. The reason is that those implementations aren't complete, even though is C++20. The recommended way to get those headers is to grab https://github.com/fmtlib/fmt as an external library... Talk about the level of hype and lack of pragmatism that's going on around here. People are accusing each other for not using a standard library that isn't even implemented in compilers... And with a likelyhood they haven't used the external library themselves, and given that this library is external it's not heavily tested and probably contains bugs still, maybe CRASHES and SECURITY EXPLOITS.

But let me test C++ features that actually exist:

   #if VERSION == 0

   #include 
   #include 

   struct membuf: std::streambuf
   {
      membuf(char *p, size_t size)
      {
         setp(p, p + size);
      }
      size_t written() { return pptr() - pbase(); }
   };

   int main()
   {
      char buffer[256];
      membuf sbuf(buffer, sizeof buffer);

      std::ostream out(&sbuf);

      out << "Hello " << 42 << "\n";

      fwrite(buffer, 1, sbuf.written(), stdout);

      return 0;
   }


   #elif VERSION == 1
   #include 
   #include 

   void test(std::stringstream& os)
   {
       os << "Hello " << 42 << "\n";
   }

   int main()
   {
      std::stringstream os;
      test(os);
      std::cout << os.str();
      return 0;
   }

   #elif VERSION == 2

   #include 

   int test(char *buffer, int size)
   {
      int r = snprintf(buffer, size, "Hello %d\n", 42);
      return r;
   }

   int main()
   {
      char buffer[256];
      int len = test(buffer, sizeof buffer);
      fwrite(buffer, 1, len, stdout);
      return 0;
   }
   #endif

Compile & link:

                   CT      LT      TT      PT      PL

     -DVERSION=0   0.361s  0.095s  0.456s  0.081s  32573
     -DVERSION=1   0.364s  0.089s  0.453s  0.074s  32338 
     -DVERSION=2   0.039s  0.088s  0.127s  0.031s    918
CT=compile time, LT=link time, TT=total time (CT+LT), PT=preproc time (gcc -E), PL=preprocessor output lines

Bench script:

    # put -DVERSION=1 or -DVERSION=2 as cmdline arg
    time clang++ -c "$@" -Wall -o test.o test.cpp
    time clang++ -Wall -o test test.o
    time clang++ "$@" -Wall -E -o test.preprocessed.txt test.cpp
    wc -l test.preprocessed.txt
My clang version here is 14.0.6. I measured with g++ 12.2.0 as well and the results were similar (with only 50% of the link time for the snprintf-only version).

For such a trivial file, the difference is ABYSMAL. If we extrapolate to real programs we can assume the difference in build times to be 5-10x longer for a general change in programming style. Wait 10 seconds or wait 1 minute. For a small gain in safety, how much are you willing to lose? And how much do this lost time and resources actually translate to working less on the robustness of the program, leaving more security problems (as well as other problems) in there?

And talking about lost run time performance, that is real too if you're not very careful.

> For snprintf(), how do you ensure that your format string and variadic arguments will not cause crash at runtime? The C++ version is compile-time type safe.

Honestly I just don't ensure it perfectly -- beyond running them once as described. I write a lot of code that isn't fully proofed out from the beginning. Exploratory code. A few printfs are really not a concern in there, there are much bigger issues to work out.

I also absolutely do have some printfs that were quickly banged out but that are hidden in branches that have never actually run and might never happen -- they were meant for some condition that I'm not even sure is possible (this happens frequently when checking return values from complicated APIs for example).

The real "problem" isn't that there is a possibly wrong printf in that branch, but that the branch was never tested, and is likely to contain other, much worse bugs. But the fact that the branch was never run also means I don't care as much about it, pragmatically speaking. Likely there is an abort() or similar at the end of the branch anyway. It's always important to put things into perspective like that -- which is something that seems often missing from C++ and similar cultures.

The more proofed out some code gets, the more scrutiny it should undergo obviously.

Apart from that, compilers do check printfs, and I usually get a warning/error when I made a mistake. But I might not get one if I write my own formatting wrappers and am too lazy to explicitly enable the checking.

iostreams are horrible to use, especially if you want to format your output and they blow up compilation to a mythical degree. People go to great lengths to avoid them and I agree with them.

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.

fmtlib (https://github.com/fmtlib/fmt), a C++11 formatting library that was the basis for C++20 std::format, is able to do this.
http://tmplbook.com/ and study the source code of some good-quality open source project, for example https://github.com/fmtlib/fmt/

and this could be also handy: https://en.cppreference.com/w/cpp/meta

A list of modern C++ related resources can be found here:

https://github.com/rigtorp/awesome-modern-cpp

The list includes some powerful yet not excessively large libraries from which one can learn including:

https://github.com/skypjack/entt

https://github.com/taocpp/PEGTL

https://github.com/fmtlib/fmt

strtod is not a good benchmark. Is it faster than std::format or https://github.com/fmtlib/fmt?
> One thing I was surprised not to see mentioned here is multiple inheritance.

There's nuance to banning that since implementating multiple interfaces is technically multiple inheritance in C++. So I think I'd agree with you but with an exception for pure-virtual classes.

> Also, while iostream is a bit of a mess, having it be strongly typed means that it's, in my opinion, well worth using over cstdio.

Orthodox C++ would ban this but you can have both printf-style with type safety with https://github.com/fmtlib/fmt

Which inspired C++20's std::format: https://en.cppreference.com/w/cpp/utility/format

Which is also banned by Orthodox C++

Are you saying the compiler itself does the checking? That is a pity. Compiler magic for a checking a fixed specific format system (I don't know about D, but that's what you're describing in C printf) is significantly worse the compile-time checking being written in the language itself.

What if you, as library author (not compiler writer!), think of a much better formatting system? Or something that's maybe not better in general, but better for an unusual situation you're deploying in? Or you want to write something that's different to a formatting system but uses similar tricks? If you don't have language facilities to do this yourself then you're stuffed.

As well the original post showing it's possible in Rust, this is also possible in C++. For example, the C++ fmt [1] library allows compile-time time checking of the number of slots in format strings against the number of arguments, and even that the format specifications (.03d or whatever) match the passed in types. And it's not reliant on the compiler doing its own magic checking outside of the language.

[1] https://github.com/fmtlib/fmt

Unfortunately performance claims appear to be bogus.

1. ospan, performance claims seem to be based on, doesn't do any bound checks, so you can easily get buffer overflow.

2. fast_io generates a whopping 50kB of static data just to format an integer.

So if these benchmark results are correct (I was not able to verify because the author hasn't provided the benchmark source):

> format_int 7867424 ns 7866027 ns 89 items_per_second=127.129M/s

> fast_io_ospan_res 6871917 ns 6870708 ns 102 items_per_second=145.545M/s

fast_io gives 15% perf improvement by replacing a safe format_int API from https://github.com/fmtlib/fmt with a similar but unsafe one + 50kB of extra data. Adding safety will likely bring perf down which the last line seems to confirm:

> fast_io_concat 7967591 ns 7966162 ns 88 items_per_second=125.531M/s

This shows that fast_io is slightly slower than the equivalent {fmt} code. Again this is from the fast_io's benchmark results that I hasn't been able to reproduce.

50kB may not seem like much but for comparison, after a recent binary size optimization, the whole {fmt} library is around 57kB when compiled with `-Os -flto`: http://www.zverovich.net/2020/05/21/reducing-library-size.ht...

The floating-point benchmark results are even less meaningful. They appear to be based on a benchmark that I wrote to test the worst case Grisu (https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/p...) performance on unrealistic random data with maximum digit count. fast_io compares it to Ryu (https://dl.acm.org/doi/pdf/10.1145/3192366.3192369) where maximum digit count is actually the best case and the performance degrades as the number of digits goes down. A meaningful thing to do would be to use Milo Yip's benchmark instead: https://github.com/miloyip/dtoa-benchmark

> Neither will C++, as far as I know, or do you mean that the compiler will validate the formatting of the string literal.

With iostreams there are no format strings so there is nothing to validate. Libraries such as https://github.com/fmtlib/fmt are allow to do compile-time parsing of the string though.

Interesting side note: https://github.com/fmtlib/fmt of the same author has been aded to the C++20 standard. One of the few inclusions into the standard which (IMHO) can be universally considered to be a good thing. If you're coming from printf() or iostreams, fmtlib is a joy to use, especially together with pystring (https://github.com/imageworks/pystring).

Both libs don't fix the underlying problems of std::string, but at least they reduce the pain of working with strings in C++ considerably.

I stopped using `<<` for the most part after I discovered the excellent fmt library[1].

[1] https://github.com/fmtlib/fmt

slavik81 showed a simpler for_each in a comment;

    auto for_each = [](R&& r, Fun fun) {
      return r | view::transform(fun) | view::join;
    };
similarly, it seems that yield_if can also be radically simplified

    auto yield_if = [](bool b, T x) {
        return b ? view::single(x)
                 : view::empty;
    };
But if you actually read the blog post... for_each and yield_if are a part of the range_v3 library and are proposed additions to the language.

Also I don't know what the heck is up with that cout statement; we can use structured declarations to make that look more pythonic too. And there's also a proposal for string formatting [1]

  #include "fmt/format.h"
  #include 
  #include 

  using namespace std;
  using namespace view;
  using namespace fmt;
  int main() {
    auto triples =
      for_each(iota(1), [](int z) {
        return for_each(iota(1, z+1), [=](int x) {
          return for_each(iota(x, z+1), [=](int y) {
            return yield_if(x*x + y*y == z*z,
              make_tuple(x, y, z));
          });
        });
      });
 
    for(auto [x,y,z] : triples | take(10))
      cout << format("{}, {}, {}", a, b, c) << endl;
  }
So despite all the protests about how ugly Eric's solution is... I don't think the above is significantly worse than your python & rust equivalents. Speaking as a python evangelist who uses C++ out of nece$$ity.

[1] https://github.com/fmtlib/fmt

Not long ago I’ve benchmarked several implementations. The fastest itoa I’ve found was fmt::FormatInt class from there https://github.com/fmtlib/fmt

Based on the article, I think fmtlib will be faster than what they did here.

I’m surprised at how the author fails to mention fmtlib [1] with near-printf-like performance and sane (Python .format-like) formatting syntax, which also happens to generate much smaller binaries than Boost.Format.

[1] https://github.com/fmtlib/fmt

Also C++ has Python-like formatting: https://github.com/fmtlib/fmt. Disclaimer: I'm the author of this library.