crow

Utility library


Project maintained by CaptainCrowbar Hosted on GitHub Pages — Theme by mattgraham

Formatting Functions

Crow Library by Ross Smith

#include "crow/format.hpp"
namespace Crow;

This header supplies the main formatting functions of the library.

Contents

Formatting rules for standard types

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.

Customization points

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.

Formatting algorithm

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:

template <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.

Format specification

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:

An 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.

Type specific formatting functions

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.

Generic formatting functions

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.

Formatter class

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"

Formatter literals

This is in namespace Crow::Literals.

Formatter operator""_fmt(const char* ptr, size_t len);

Returns Formatter(std::string(ptr, len)).

Formatted I/O functions

The function signatures below are simplified for clarity. Ellipses represent variadic template arguments, not old-style C varargs.

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.