Format¶
Au supports string formatting using either the {fmt} library, or else std::format (available in C++20 or later).
Warning: if using {fmt}, ensure version is at least 9.0
If you are using the {fmt} library, make sure that it is at least version 9.0 (July 5, 2022).
All earlier versions provide automatic specializations for formatter<T>
for any type T
that
supports streaming output. While this is convenient, it also makes it extremely easy to violate
the One Definition Rule (ODR): if anyone calls format
in a file without Au’s specialization,
then both definitions will exist. This makes the program “ill-formed, no diagnostic required”
(IFNDR), which means that it has silently failed to be a valid C++ program, and all
guarantees are out the window.
Version 9.0 was the first release to make the default specialization cause a hard compiler error. This mitigates the risk of having two definitions, undetected.
As for std::format
, it has always required explicit specializations, so it is not subject to
this vulnerability.
We can’t provide fully seamless support out of the box without adding an external dependency — and
Au is committed to being a zero-dependency library. What we can do is to define the format string
specification, and take the hard work out of implementing it. To use it in your project, you’ll
create a single file, and write a single line of code. Then, you’ll include that file anywhere you
want to format a Quantity
.
Setup¶
The setup depends on whether you are using std::format
, or the {fmt} library.
Using std::format
¶
Include "au/std_format.hh"
.
Note that this will not work unless you are building on a toolchain that fully supports
std::format
and the <format>
header.
Using {fmt}
¶
There are two steps to support formatting in your project:
-
Create a file to hold the authoritative formatter definition.
-
Include this file anywhere you want to format a
Quantity
.
Here are the contents of the file to create:
#pragma once
#include "au/au.hh"
#include "fmt/format.h"
namespace fmt {
template <typename U, typename R>
struct formatter<::au::Quantity<U, R>> : ::au::QuantityFormatter<U, R, ::fmt::formatter> {};
} // namespace fmt
Tip: finding the file in your project
It’s very important to have only one definition of formatter<Quantity<U, R>>
in your
project. This means you need a way to tell whether someone has created this file already, and
find it if it exists.
Fortunately, the string QuantityFormatter
is very greppable. In most cases, it should only
occur once in your project — even if there are a few stray mentions, it should be easy to find
the authoritative definition. Start by grepping your project for this string. If you find it,
you can just use the file. If not, you know you need to create it.
Syntax¶
Printing a Quantity<U, R>
means printing its numeric value, followed by its unit label. Au offers
some degree of formatting control for each of these.
- For the numeric part, the syntax is exactly the same as for formatting the underlying
representation type
R
. For example, ifR
isdouble
, you can use any of the standard format specifiers fordouble
. - For the unit label, we offer very limited control: only a minimum label string width. If the
label is shorter than this, we will pad it on the right with spaces. The syntax is a
U
(for “unit label”) followed by digits representing the minimum width. - If both are present, then the unit label format string must come first, and they must be separated
by a
;
.
It may be easier to understand with several examples.
Specialize format for | Pattern | Example | Output |
---|---|---|---|
Neither | {} |
fmt::format("{}", meters(123.456)) |
"123.456 m" |
Numeric value | {:~^10.2f} |
fmt::format("{:~^10.2f}", meters(123.456)) |
"~~123.46~~ m" |
Unit label | {:U5} |
fmt::format("{:U5}", meters(123.456)) |
"123.456 m " |
Both | {:U5;~^10.2f} |
fmt::format("{:U5;~^10.2f}", meters(123.456)) |
"~~123.46~~ m " |
To target a specific and consistent overall width — say, for aligning columnar output — provide formatters for both the unit label and the numeric value, and choose a specific width for each. The overall width will be the sum of the widths of the two pieces, plus one space in between.
Specialized use cases¶
The core support provides a formatted numeric value, then a space ' '
, the unit label, and
optional padding. There may be other use cases, but we don’t support them directly, and we don’t
plan to. Fortunately, these tend to have straightforward workarounds.
Here are some examples.
Besides the separate formatters for the numeric value and unit label, some users may wish to format
the “overall result”. For example, they may want the whole quantity (unit and label) to be centered
in a width of, say, 25 characters. We cannot support this directly without excessive complexity —
both in the implementation, and in the formatter syntax. The simple workaround is to format your
quantity as desired using the existing syntax, and then format the result using simple std::string
formatting, like so:
constexpr auto speed = (centi(meters) / second)(987.654321);
std::cout << fmt::format("{:.^31}", fmt::format("{:U10;,<8.2f}", speed));
// Output: "......987.65,, cm / s ......"
Some users may want a format that prints only the numeric value, or only the unit label. We don’t provide a direct way to do this, because neither of these pieces is meaningful on its own. If you do have a use case for formatting only one of these, the workaround is straightforward. Simply retrieve the numeric value or unit label by the library’s standard methods, and pass the result to a formatter for that numeric type, like so: