For many years we have used Chris Foster's magnificent "tinyformat"
package to do a lot of the heavy lifting for our type-safe string
formatting with printf-like notation, which we use for Strutil::format,
nearly every error reporting facility in the major classes, etc. Thanks,
Chris!
The C++ committee has finally caught up to Chris, advancing drafts of a
"string formatting" proposal that now seems very likely to be part of
C++20. The best implementation at this point is the {fmt} package.
Unfortunately, {fmt} and the C++ draft papers differ from tinyformat in
that rather than traditional C printf notation for formatting, they use
a notation very similar to Python string formatting, e.g.,
format ("%d %.3f", 1, 23.4) --> format ("{} {.3f}", 1, 23.4)
This compatibility break is one reason I've stayed away from {fmt},
despite that (no slight to Chris) it's at least twice twice the speed of
tinyformat.
But knowing that this is almost certainly the shape of the future C++
standard, I am confident that the best long-term path is to migrate to
its conventions. And it will be nice to reuse the same cognitive
machinery whether we're programming in C++ or Python, and get the speed
boost.
But how to do this migration without breaking every string format
operation in OIIO, and additionally in all the packages that call OIIO
and use any of its string formatting or error reporting facilities (and
indeed, we use it extensively in OSL)? This PR is the first step of
implementing the transition, which will occur over a couple major
releases. Here we go:
1. Introduce Strutil::sprintf(), ustring::sprintf(), and various
errorf(), warningf(), etc., functions as needed that now and forever
will use the printf notation (and for now are still implemented
primarily with tinyformat, but that's not a detail you need to worry
about).
Neary all internal uses of the format() commands within OIIO internals
have switched to sprintf().
Calling apps that make use of OIIO's string formatting can therefore
also change their calls from format() to sprintf() and everything
will continue working for the forseeable future.
2. Introduce, in a namespace, Strutil::fmt::format() and certain
fmterror(), etc., that use the NEW Python-like notation, implemented
underneath with fmt::format().
Calling apps may start using these now if they prefer the Python
notation...
But BEWARE! I have discovered that the fmt library does not correctly
implement the promised locale-inedependent formatting for floating
point values (it does for integers), and so could cause problems for
those of you who might have apps running on platforms wth certain
locales that, for example, use ',' as the decimal (sheesh). This is a
known problem with fmt, will be fixed soon, and as soon as it does,
I'll update our embedded copy of fmt and give you the all clear to go
nuts with the new python style formatting directives.
3. For OIIO 2.0, Strutil::format, ustring::format, and the various error()
functions will behave identically to sprintf (C printf notation), but
for OIIO 2.1, I am planning on having them all switch to to the new
python (a.k.a. C++20-ish std::format()) format designations.
So, the upshot is that NOTHING should break for your existing code's
format()/error() calls when switching from OIIO 1.x to 2.0.
(Proof: OSL master compiles against it and passes all tests, without a
single change.)
But after you upgrade to 2.0 and before 2.1 is released (I'm assuming in
the latter half of 2019 at the earliest), you'll want to either switch
all those calls to Strutil::sprintf()/errorf() and stick to the
printf-style formatting *OR* change them to
Strutil::fmt::format()/fmterror() using the new formatting specs.
Pretty much all of the OIIO internals (error calls, etc.) have been
changed to use the explicit printf-like forms. So they seem safe.
After 2.1, then, the old names format()/error() will be the new behavior
and you can switch back to those if you want. And by the time C++20
comes out, we'll all be on the same page, and some day OIIO won't even
need to use the fmt library because it can rely on std::format.
There you go. Here is the PR that implements the first phase.
We have put the relevant parts of fmt library in
include/OpenImageIO/fmt. We are currently using commit 01640f44, with
one fix by me on top of it that fixes a Windows compile issue. We will
update the version of fmt as fixes and improvements come.
Sorry for such a weird change in the middle of the beta period. But
sometimes a looming deadline of a code freeze and major release (after
which I wouldn't want to introduce this kind of disrupction) really
makes one focus about doing it right now instead of having to wait
potentially an additional year.