Utility library
#include "crow/format.hpp"
namespace Crow;
This header supplies the main formatting functions of the library.
Format specification strings are parsed by the FormatSpec
class (see below).
A format string takes the form "<mode>[<options>][<prec>]"
. It consists of
a single-letter mode, zero or more options, and an optional precision
(an unsigned integer). The mode and options are restricted to ASCII letters,
and are case sensitive. An empty string is also a valid format spec; this has
a default interpretation specific to the type being formatted.
Types not listed here are simply converted to strings by whatever means is available (this is described in more detail below), with no formatting control; any format specification supplied is ignored. User defined types can be formatted by use of the customization points described below.
T
= Return the name of the argument’s type instead of formatting its valueu
= With T
mode, show the unqualified nameZ
= Use "--"
in place of a null value (zero, an empty string, or a null pointer)const
qualifiers are stripped from the type because
the fully qualified original type of a function argument can’t be
easily retrievedstring
N
= Leave out the enclosing bracketsstd::nullptr_t
and all pointer types except [const] char*
"<null>"
(unless the Z
option is used)"<0xHHH...>"
(pointer value in hex)bool
B,b
= "True/False"
or "true/false"
Y,y
= "Yes/No"
or "yes/no"
"b"
A,a
= Use apostrophes for digit grouping (C++ convention)C,c
= Use commas for digit grouping (English convention)U,u
= Use underscores for digit grouping (Ada convention)W,w
= Use whitespace for digit grouping (SI convention)E
= Elide delimiter if there is one more than a single group (e.g. "1234"
but "12,345"
)s
= Always show a signB,b
= Binary integerN,n
= Decimal integerR,r
= Roman numerals (throws invalid_argument
if less than 1, or 0 if Z
or z
is set)X,x
= Hexadecimal integerz
= With R/r
, use 0
for zero."n1"
R/r
and X/x
formats, the case of the mode is used for outputD,d
= Decimal formatE,e
= Exponential (scientific) formatF,f
= Fixed point formatG,g
= Use D/d
if 1e-3<=|value|<1e6
or value=0
, otherwise E/e
P,p
= Probability formatting (see below)e
= Use a comma for the decimal point (European convention)S
= Always show a sign on the exponentz
= Strip trailing zeroesF/f
formats, this is the number of decimal places to display"gz6"
E/e
and G/g
formats, the case of the mode is used for outputP
format multiplies by 100 to show the value as a percentagestd::domain_error
if the argument is not 0-1std::complex<T>
where T
is a floating point typei
= Use x+yi
formatj
= Use x+yj
formatp
= Use polar format (r,θ)
with -π<θ≤π
P
= Use polar format (r,θ)
with 0≤θ<2π
o
= Show only one component if the other is zero(x,y)
std::string
and const char*
q
= Quote the string, with escapes (e.g. "\"Hello\\n\""
)s
= Just print the literal string (default)X,x
= Expand the string as hex byte values (e.g. "48 65 6c 6c 6f 0a"
)z
= No spaces in X/x
format"<null>"
std::chrono::duration
s
= Format as seconds (the common numerical options also apply here)t
= Expanded time format (e.g. "12h34m56s"
) (default)std::chrono::system_clock::time_point
d
= Day, month, year, time (e.g. "3 Feb 2021 04:05:06"
)D
= Weekday, day, month, year, time (e.g. "Wed 3 Feb 2021 04:05:06"
)i
= ISO 8601 with space delimiter (e.g. "2021-02-03 04:05:06"
) (default)I
= ISO 8601 with T delimiter (e.g. "2021-02-03T04:05:06"
)d
= Show only the date (no time of day)l
= Use the local time zone (default is UTC)t
= Show only the time of day (no date)Formatting for a user defined type can be configured in any of three ways:
(1) Derive your class from Formatted
:
class Formatted {
virtual ~Formatted() noexcept;
virtual std::string str(const FormatSpec& spec = {}) const = 0;
};
std::string to_string(const Formatted& obj, const FormatSpec& spec = {});
std::ostream& operator<<(std::ostream& out, const Formatted& obj);
Any class derived from Formatted
must implement a str()
function that
formats an object according to the FormatSpec
. For any class T
derived
from Formatted
, instantiations of FormatType
, to_string(T)
, and
operator<<(ostream&,T)
are automatically defined.
(2) Specialise the FormatType
template:
template <typename T> struct FormatType {
std::string operator()(const T& t, const FormatSpec& spec) const;
}
This template defines the basic formatting operation for any formattable types. It handles all predefined format types (listed below), and can be fully or partially specialized for user defined types.
(3) Give your class a str()
method:
std::string T::str() const;
std::string T::str(const FormatSpec& spec) const;
Either or both of these signatures may be used: the str()
function can use a
FormatSpec
or just yield a single format if no options are required. If both
are present, the second version will be used.
Many common ways of providing formatting for a type are recognised. The
following table is used to determine how to format an object. The first rule
that matches the object being formatted will be used; subsequent rules are
not checked even if they would also have matched. The “Spec?” column
indicates whether or not the FormatSpec
is used if supplied.
Rule | Formatting | Spec? |
---|---|---|
Format spec uses T mode |
Type name as described above | No |
std::nullptr_t |
"<null>" |
No |
Any of the standard ordering types | Unqualified constant name | No |
std::optional<T> |
"<null>" if empty, otherwise as for T |
Yes |
bool |
Boolean formatting as described above | Yes |
char |
Format as a one-character string | Yes |
char16_t , char32_t , or wchar_t |
Format as a UTF-8 string | Yes |
std::is_integral<T> |
Integer formatting as described above | Yes |
std::is_floating_point<T> |
Floating point formatting as described above | Yes |
Instantiation of std::chrono::duration |
Time span formatting as described above | Yes |
std::chrono::system_clock::time_point |
Date formatting as described above | Yes |
Derived from Formatted |
Call t.str() |
Yes |
Has a str(FormatSpec) method |
Call t.str() |
Yes |
Has a str() method |
Call t.str() |
No |
to_string(T) function (ADL or std ) |
Call to_string(T) |
No |
String-like type (see below) | String formatting as described above | Yes |
Explicitly convertible to std::string |
Return converted string without formatting | No |
Derived from std::exception |
Call t.what() |
No |
Map-like range type | Call format_map(T) |
Yes |
Range type | Call format_range(T) |
Yes |
Pointer type | "<null>" if null, otherwise address in hex |
No |
std::ostream << T is valid |
Format using std::ostringstream |
No |
Otherwise | Return the type and address | No |
For the purposes of this algorithm, a “string-like type” is defined to be any of the following:
std::string
std::string_view
char*
or const char*
char
char16_t
, char32_t
, or wchar_t
equivalents of any of the abovetemplate <typename T> concept FixedFormatType;
template <typename T> concept VariableFormatType;
FixedFormatType
is satisfied by any type that matches any of the clauses in
the algorithm above, except the runtime check for T
format and the final
fallback option. VariableFormatType
is satisfied if a FormatSpec
is
respected.
class FormatSpec;
This class parses a formatting specification string.
FormatSpec::FormatSpec(const std::string& str);
FormatSpec::FormatSpec(const char* str);
Parse a format spec containing a mode, zero to many options, and an optional
precision, as described above. This will throw std::invalid_argument
if the
string is not a valid format spec. A valid spec consists of:
'*'
to select the default modeAn empty string is also a valid format spec. When a precision is supplied as
an int
(instead of a substring), any negative value is interpreted as the
default precision.
In general, if multiple inconsistent options are present, it is unspecified which one will be respected. Formatting rules for specific types will document exceptions to this.
FormatSpec::FormatSpec(char m, const std::string& o, int p = -1);
Construct a format spec from a mode, a string of options, and a precision.
This will throw std::invalid_argument
if m
is not a letter (or a star or
null character, representing the default mode), or if o
contains any
non-letter characters.
FormatSpec::FormatSpec(int p) noexcept;
Construct a format spec from a precision alone, defaulting everything else.
FormatSpec::FormatSpec();
FormatSpec::FormatSpec(const FormatSpec& spec);
FormatSpec::FormatSpec(FormatSpec&& spec) noexcept;
FormatSpec::~FormatSpec() noexcept;
FormatSpec& FormatSpec::operator=(const FormatSpec& spec);
FormatSpec& FormatSpec::operator=(FormatSpec&& spec) noexcept;
Other life cycle functions.
bool FormatSpec::empty() const noexcept;
True if this is a default spec containing no user specified elements.
char FormatSpec::mode() const noexcept;
char FormatSpec::lcmode() const noexcept;
char FormatSpec::find_mode(std::string_view chars) const noexcept;
void FormatSpec::default_mode(char m);
void FormatSpec::set_mode(char m);
void FormatSpec::exclude_mode(std::string_view chars) const;
Mode functions. The mode()
function returns the mode letter; lcmode()
converts it to lower case. These will return a null character if the original
format spec was empty or the mode was '*'
. If the mode letter is on the list
passed to find_mode()
, it returns the mode; otherwise, it returns a null
character.
The default_mode()
function sets the mode to the given value if no mode was
originally supplied (or the mode was '*'
); it does nothing if the spec
already has a non-empty mode. The set_mode()
function sets the mode
unconditionally. Both of these will throw std::invalid_argument
if the
argument is not an ASCII letter.
The exclude_mode()
function will throw std::invalid_argument
if the mode
matches any of the listed characters.
bool FormatSpec::option(char c) const noexcept;
std::string FormatSpec::options() const;
char FormatSpec::find_option(std::string_view chars) const noexcept;
void FormatSpec::no_option(std::string_view chars) noexcept;
void FormatSpec::exclude_option(std::string_view chars) const;
Option functions. The option()
function returns true if the option code is
present. The options()
function returns the full option string. If any
option letter on the list supplied to find_option()
is present, it returns
the first matching option; otherwise, it returns a null character.
The no_option()
function removes all matching option letters from the spec.
The exclude_mode()
function will throw std::invalid_argument
if any of
the listed option characters are present.
int FormatSpec::prec() const noexcept;
bool FormatSpec::has_prec() const noexcept;
void FormatSpec::default_prec(int p) noexcept;
void FormatSpec::set_prec(int p) noexcept;
Precision functions. The prec()
function returns the precision, or -1 if no
precision was supplied. The has_prec()
function returns true if an explicit
precision was specified.
If no precision was supplied (i.e. if the precision is -1), default_prec()
sets the precision to the given value. If a non-negative precision has already
been set, this will not change it. The set_prec()
function unconditionally
sets the precision.
std::string FormatSpec::str() const;
Returns the format spec as a string. Even if no modifying functions have been called, this is not guaranteed to return the exact same string that was supplied to the constructor, but it will be semantically equivalent.
std::string format_boolean(bool b, const FormatSpec& spec);
template <std::integral T> std::string format_integer(T t,
FormatSpec spec = {});
template <ArithmeticType T> std::string format_floating_point(T t,
FormatSpec spec = {});
template <std::floating_point T> std::string format_complex(std::complex<T> t,
FormatSpec spec = {});
template <std::floating_point T> std::string format_complex(T t,
FormatSpec spec = {});
template <std::integral T> std::string format_number(T t,
FormatSpec spec = {});
template <std::floating_point T> std::string format_number(T t,
FormatSpec spec = {});
template <std::floating_point T> std::string format_number(std::complex<T> t,
FormatSpec spec = {});
Arithmetic type formatting functions. These format their arguments according
to the rules laid out above. The format_number()
function will call
whichever of the more specific functions is appropriate to the argument
type.
std::string format_string(const std::string& str,
const FormatSpec& spec = {});
std::string format_string(const std::string_view& str,
const FormatSpec& spec = {});
std::string format_string(const char* str,
const FormatSpec& spec = {});
std::string format_string(const std::u16string& str,
const FormatSpec& spec = {});
std::string format_string(const std::u16string_view& str,
const FormatSpec& spec = {});
std::string format_string(const char16_t* str,
const FormatSpec& spec = {});
std::string format_string(const std::u32string& str,
const FormatSpec& spec = {});
std::string format_string(const std::u32string_view& str,
const FormatSpec& spec = {});
std::string format_string(const char32_t* str,
const FormatSpec& spec = {});
std::string format_string(const std::wstring& str,
const FormatSpec& spec = {});
std::string format_string(const std::wstring_view& str,
const FormatSpec& spec = {});
std::string format_string(const wchar_t* str,
const FormatSpec& spec = {});
String formatting functions. All of these use the string formatting rules laid out above. UTF-32 strings are first converted to UTF-8.
class FormatObject {
FormatObject();
explicit FormatObject(const FormatSpec& spec);
template <typename T> std::string operator()(const T& t) const;
FormatSpec spec() const;
};
template <typename T> std::string format_object(const T& t,
const FormatSpec& spec = {});
A unified object formatter that calls FormatType<T>()(t, spec)
. This is
available as either a function object or a function template.
template <typename Range> std::string format_range(const Range& r,
const FormatSpec& spec = {});
template <typename Range> std::string format_map(const Range& r,
const FormatSpec& spec = {});
template <typename Range> std::string format_map(const Range& r,
const FormatSpec& spec1, const FormatSpec& spec2);
These format a sequential range, or a map-like range. A range is anything that
could be used in a range-based for
statement; a map-like range is any range
whose elements are a std::pair
or anything else that has first
and
second
data members.
Ordinary ranges use a format based on JSON arrays:
vector { 1, 2, 3, 4, 5 } => "[1,2,3,4,5]"
Map-like ranges use a format based on JSON objects:
map { { 1, "abc" }, { 2, "def" }, { 3, "ghi" } } => "{1:abc,2:def,3:ghi}"
The first version of format_map()
uses the same format spec for both fields;
the second accepts separate specs for each value. Only the first version is
used by format_object()
.
template <typename T> std::string format_via_stream(const T& t);
Formats an object with an output operator, by writing it to a
std::ostringstream
and reading the string back.
class Formatter {
Formatter();
explicit Formatter(const std::string& pattern);
Formatter(const Formatter& f);
Formatter(Formatter&& f) noexcept;
~Formatter() noexcept;
Formatter& operator=(const Formatter& f);
Formatter& operator=(Formatter&& f) noexcept;
template <typename... Args>
std::string operator()(const Args&... args) const;
};
template <typename... Args>
std::string fmt(const std::string& pattern, const Args&... args);
This class parses a format string, and then inserts a set of arguments into that string, returning the result.
A format string contains substitution fields enclosed in braces. Each field consists of a number, indicating which argument goes in here (zero-based indexing), optionally followed by a colon and a format spec. Backslashes can be used to escape braces that are intended to be taken literally.
The constructor will throw std::invalid_argument
if the format string is
malformed(mismatched braces or an invalid format spec). The function call
operator will throw std::invalid_argument
if there are not enough arguments
to match the fields in the format string.
The fmt()
function simply calls Formatter(pattern)(args...)
.
Example:
fmt("Hello {2} {0} {1:f3}", 123, 456, "world");
=> "Hello world 123 456.000"
This is in namespace Crow::Literals
.
Formatter operator""_fmt(const char* ptr, size_t len);
Returns Formatter(std::string(ptr, len))
.
The function signatures below are simplified for clarity. Ellipses represent variadic template arguments, not old-style C varargs.
FMT = std::string format_pattern
OS = std::ostream&
FP = FILE*
Function | Function |
---|---|
void printc(FMT, ...) |
void printcf(FMT, ...) |
void printct(OS, FMT, ...) |
void printcft(OS, FMT, ...) |
void printct(FP, FMT, ...) |
void printcft(FP, FMT, ...) |
void printc() |
void printcf() |
void printct(OS) |
void printcft(OS) |
void printct(FP) |
void printcft(FP) |
void printp(...) |
void printpf(...) |
void printpt(OS, ...) |
void printpft(OS, ...) |
void printpt(FP, ...) |
void printpft(FP, ...) |
void printp() |
void printpf() |
void printpt(OS) |
void printpft(OS) |
void printpt(FP) |
void printpft(FP) |
Functions with a c
suffix take a formatting string, followed by a list of
arguments to format. Functions with a p
suffix print their arguments,
formatted with format_object()
, and delimited by spaces. The suffixes are
intended to suggest “works like C printf()
” and “works like Python
print()
”.
Functions with an f
suffix flush the output stream after writing.
Functions with a t
suffix take an output stream or file pointer as their
first argument (the suffix stands for “to…”). Functions that do not take an
output stream write to standard output.
A line feed will be added to the output, if the output text did not already end with one. The versions that take no arguments other than an output stream just write a line break.
Output is atomic with respect to other threads; the text written by any call will not be interleaved with any output from another thread.
template <typename... Args> std::string prints(const Args&... args);
This formats a space delimited list of arguments, in the same way as
printp()
, but without the terminating line feed, and returns the formatted
string instead of writing it out.