Add support of most format_specs for formatting at compile-time (#2056)

This commit is contained in:
Alexey Ochapov 2020-12-25 17:40:03 +03:00 committed by GitHub
parent a750bf3ac6
commit bbd6ed5bc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 182 additions and 121 deletions

View File

@ -466,10 +466,10 @@ struct is_compiled_format<field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers. // A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field { template <typename Char, typename T, int N> struct spec_field {
using char_type = Char; using char_type = Char;
mutable formatter<T, Char> fmt; formatter<T, Char> fmt;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const { constexpr OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`. // This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...); const T& arg = get<N>(args...);
const auto& vargs = const auto& vargs =

View File

@ -283,6 +283,14 @@ struct monostate {};
namespace detail { namespace detail {
constexpr bool is_constant_evaluated() FMT_NOEXCEPT {
#ifdef __cpp_lib_is_constant_evaluated
return std::is_constant_evaluated();
#else
return false;
#endif
}
// A helper function to suppress "conditional expression is constant" warnings. // A helper function to suppress "conditional expression is constant" warnings.
template <typename T> constexpr T const_check(T value) { return value; } template <typename T> constexpr T const_check(T value) { return value; }
@ -481,7 +489,7 @@ inline basic_string_view<Char> to_string_view(
} }
template <typename Char> template <typename Char>
inline basic_string_view<Char> to_string_view(basic_string_view<Char> s) { constexpr basic_string_view<Char> to_string_view(basic_string_view<Char> s) {
return s; return s;
} }
@ -938,9 +946,9 @@ struct arg_data<T, Char, NUM_ARGS, 0> {
T args_[NUM_ARGS != 0 ? NUM_ARGS : +1]; T args_[NUM_ARGS != 0 ? NUM_ARGS : +1];
template <typename... U> template <typename... U>
FMT_INLINE arg_data(const U&... init) : args_{init...} {} FMT_CONSTEXPR arg_data(const U&... init) : args_{init...} {}
FMT_INLINE const T* args() const { return args_; } FMT_CONSTEXPR const T* args() const { return args_; }
FMT_INLINE std::nullptr_t named_args() { return nullptr; } FMT_CONSTEXPR std::nullptr_t named_args() { return nullptr; }
}; };
template <typename Char> template <typename Char>
@ -961,7 +969,7 @@ void init_named_args(named_arg_info<Char>* named_args, int arg_count,
} }
template <typename... Args> template <typename... Args>
FMT_INLINE void init_named_args(std::nullptr_t, int, int, const Args&...) {} FMT_CONSTEXPR void init_named_args(std::nullptr_t, int, int, const Args&...) {}
template <typename T> struct is_named_arg : std::false_type {}; template <typename T> struct is_named_arg : std::false_type {};
@ -1073,17 +1081,20 @@ template <typename Context> class value {
constexpr FMT_INLINE value(int val = 0) : int_value(val) {} constexpr FMT_INLINE value(int val = 0) : int_value(val) {}
constexpr FMT_INLINE value(unsigned val) : uint_value(val) {} constexpr FMT_INLINE value(unsigned val) : uint_value(val) {}
FMT_INLINE value(long long val) : long_long_value(val) {} constexpr FMT_INLINE value(long long val) : long_long_value(val) {}
FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {}
FMT_INLINE value(int128_t val) : int128_value(val) {} FMT_INLINE value(int128_t val) : int128_value(val) {}
FMT_INLINE value(uint128_t val) : uint128_value(val) {} FMT_INLINE value(uint128_t val) : uint128_value(val) {}
FMT_INLINE value(float val) : float_value(val) {} FMT_INLINE value(float val) : float_value(val) {}
FMT_INLINE value(double val) : double_value(val) {} FMT_INLINE value(double val) : double_value(val) {}
FMT_INLINE value(long double val) : long_double_value(val) {} FMT_INLINE value(long double val) : long_double_value(val) {}
FMT_INLINE value(bool val) : bool_value(val) {} constexpr FMT_INLINE value(bool val) : bool_value(val) {}
FMT_INLINE value(char_type val) : char_value(val) {} constexpr FMT_INLINE value(char_type val) : char_value(val) {}
FMT_INLINE value(const char_type* val) { string.data = val; } FMT_CONSTEXPR value(const char_type* val) {
FMT_INLINE value(basic_string_view<char_type> val) { string.data = val;
if (is_constant_evaluated()) string.size = {};
}
FMT_CONSTEXPR value(basic_string_view<char_type> val) {
string.data = val.data(); string.data = val.data();
string.size = val.size(); string.size = val.size();
} }
@ -1406,7 +1417,7 @@ class locale_ref {
const void* locale_; // A type-erased pointer to std::locale. const void* locale_; // A type-erased pointer to std::locale.
public: public:
locale_ref() : locale_(nullptr) {} constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale> explicit locale_ref(const Locale& loc); template <typename Locale> explicit locale_ref(const Locale& loc);
explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; } explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; }
@ -1437,7 +1448,7 @@ template <typename T> int check(unformattable) {
"formatter<T> specialization: https://fmt.dev/latest/api.html#udt"); "formatter<T> specialization: https://fmt.dev/latest/api.html#udt");
return 0; return 0;
} }
template <typename T, typename U> inline const U& check(const U& val) { template <typename T, typename U> constexpr const U& check(const U& val) {
return val; return val;
} }
@ -1446,7 +1457,7 @@ template <typename T, typename U> inline const U& check(const U& val) {
// another (not recommended). // another (not recommended).
template <bool IS_PACKED, typename Context, type, typename T, template <bool IS_PACKED, typename Context, type, typename T,
FMT_ENABLE_IF(IS_PACKED)> FMT_ENABLE_IF(IS_PACKED)>
inline value<Context> make_arg(const T& val) { constexpr value<Context> make_arg(const T& val) {
return check<T>(arg_mapper<Context>().map(val)); return check<T>(arg_mapper<Context>().map(val));
} }
@ -1480,28 +1491,30 @@ template <typename OutputIt, typename Char> class basic_format_context {
Constructs a ``basic_format_context`` object. References to the arguments are Constructs a ``basic_format_context`` object. References to the arguments are
stored in the object so make sure they have appropriate lifetimes. stored in the object so make sure they have appropriate lifetimes.
*/ */
basic_format_context(OutputIt out, constexpr basic_format_context(
basic_format_args<basic_format_context> ctx_args, OutputIt out, basic_format_args<basic_format_context> ctx_args,
detail::locale_ref loc = detail::locale_ref()) detail::locale_ref loc = detail::locale_ref())
: out_(out), args_(ctx_args), loc_(loc) {} : out_(out), args_(ctx_args), loc_(loc) {}
format_arg arg(int id) const { return args_.get(id); } constexpr format_arg arg(int id) const { return args_.get(id); }
format_arg arg(basic_string_view<char_type> name) { return args_.get(name); } FMT_CONSTEXPR format_arg arg(basic_string_view<char_type> name) {
return args_.get(name);
}
int arg_id(basic_string_view<char_type> name) { return args_.get_id(name); } int arg_id(basic_string_view<char_type> name) { return args_.get_id(name); }
const basic_format_args<basic_format_context>& args() const { return args_; } const basic_format_args<basic_format_context>& args() const { return args_; }
detail::error_handler error_handler() { return {}; } FMT_CONSTEXPR detail::error_handler error_handler() { return {}; }
void on_error(const char* message) { error_handler().on_error(message); } void on_error(const char* message) { error_handler().on_error(message); }
// Returns an iterator to the beginning of the output range. // Returns an iterator to the beginning of the output range.
iterator out() { return out_; } FMT_CONSTEXPR iterator out() { return out_; }
// Advances the begin iterator to ``it``. // Advances the begin iterator to ``it``.
void advance_to(iterator it) { void advance_to(iterator it) {
if (!detail::is_back_insert_iterator<iterator>()) out_ = it; if (!detail::is_back_insert_iterator<iterator>()) out_ = it;
} }
detail::locale_ref locale() { return loc_; } FMT_CONSTEXPR detail::locale_ref locale() { return loc_; }
}; };
template <typename Char> template <typename Char>
@ -1550,7 +1563,7 @@ class format_arg_store
: 0); : 0);
public: public:
format_arg_store(const Args&... args) FMT_CONSTEXPR format_arg_store(const Args&... args)
: :
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
basic_format_args<Context>(*this), basic_format_args<Context>(*this),
@ -1571,7 +1584,7 @@ class format_arg_store
\endrst \endrst
*/ */
template <typename Context = format_context, typename... Args> template <typename Context = format_context, typename... Args>
inline format_arg_store<Context, Args...> make_format_args( constexpr format_arg_store<Context, Args...> make_format_args(
const Args&... args) { const Args&... args) {
return {args...}; return {args...};
} }
@ -1644,25 +1657,27 @@ template <typename Context> class basic_format_args {
const format_arg* args_; const format_arg* args_;
}; };
bool is_packed() const { return (desc_ & detail::is_unpacked_bit) == 0; } constexpr bool is_packed() const {
return (desc_ & detail::is_unpacked_bit) == 0;
}
bool has_named_args() const { bool has_named_args() const {
return (desc_ & detail::has_named_args_bit) != 0; return (desc_ & detail::has_named_args_bit) != 0;
} }
detail::type type(int index) const { FMT_CONSTEXPR detail::type type(int index) const {
int shift = index * detail::packed_arg_bits; int shift = index * detail::packed_arg_bits;
unsigned int mask = (1 << detail::packed_arg_bits) - 1; unsigned int mask = (1 << detail::packed_arg_bits) - 1;
return static_cast<detail::type>((desc_ >> shift) & mask); return static_cast<detail::type>((desc_ >> shift) & mask);
} }
basic_format_args(unsigned long long desc, constexpr basic_format_args(unsigned long long desc,
const detail::value<Context>* values) const detail::value<Context>* values)
: desc_(desc), values_(values) {} : desc_(desc), values_(values) {}
basic_format_args(unsigned long long desc, const format_arg* args) constexpr basic_format_args(unsigned long long desc, const format_arg* args)
: desc_(desc), args_(args) {} : desc_(desc), args_(args) {}
public: public:
basic_format_args() : desc_(0) {} constexpr basic_format_args() : desc_(0), args_(nullptr) {}
/** /**
\rst \rst
@ -1670,7 +1685,7 @@ template <typename Context> class basic_format_args {
\endrst \endrst
*/ */
template <typename... Args> template <typename... Args>
FMT_INLINE basic_format_args(const format_arg_store<Context, Args...>& store) constexpr basic_format_args(const format_arg_store<Context, Args...>& store)
: basic_format_args(store.desc, store.data_.args()) {} : basic_format_args(store.desc, store.data_.args()) {}
/** /**
@ -1679,7 +1694,7 @@ template <typename Context> class basic_format_args {
`~fmt::dynamic_format_arg_store`. `~fmt::dynamic_format_arg_store`.
\endrst \endrst
*/ */
FMT_INLINE basic_format_args(const dynamic_format_arg_store<Context>& store) constexpr basic_format_args(const dynamic_format_arg_store<Context>& store)
: basic_format_args(store.get_types(), store.data()) {} : basic_format_args(store.get_types(), store.data()) {}
/** /**
@ -1687,12 +1702,12 @@ template <typename Context> class basic_format_args {
Constructs a `basic_format_args` object from a dynamic set of arguments. Constructs a `basic_format_args` object from a dynamic set of arguments.
\endrst \endrst
*/ */
basic_format_args(const format_arg* args, int count) constexpr basic_format_args(const format_arg* args, int count)
: basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count),
args) {} args) {}
/** Returns the argument with the specified id. */ /** Returns the argument with the specified id. */
format_arg get(int id) const { FMT_CONSTEXPR format_arg get(int id) const {
format_arg arg; format_arg arg;
if (!is_packed()) { if (!is_packed()) {
if (id < max_size()) arg = args_[id]; if (id < max_size()) arg = args_[id];

View File

@ -248,9 +248,6 @@ const typename basic_data<T>::digit_pair basic_data<T>::digits[] = {
{'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
{'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}}; {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
template <typename T>
const char basic_data<T>::hex_digits[] = "0123456789abcdef";
#define FMT_POWERS_OF_10(factor) \ #define FMT_POWERS_OF_10(factor) \
factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \
(factor)*1000000, (factor)*10000000, (factor)*100000000, \ (factor)*1000000, (factor)*10000000, (factor)*100000000, \
@ -1071,10 +1068,13 @@ const char basic_data<T>::background_color[] = "\x1b[48;2;";
template <typename T> const char basic_data<T>::reset_color[] = "\x1b[0m"; template <typename T> const char basic_data<T>::reset_color[] = "\x1b[0m";
template <typename T> const wchar_t basic_data<T>::wreset_color[] = L"\x1b[0m"; template <typename T> const wchar_t basic_data<T>::wreset_color[] = L"\x1b[0m";
template <typename T> const char basic_data<T>::signs[] = {0, '-', '+', ' '}; template <typename T> const char basic_data<T>::signs[] = {0, '-', '+', ' '};
#if __cplusplus < 201703L
template <typename T> constexpr const char basic_data<T>::hex_digits[];
template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
template <typename T> template <typename T>
const char basic_data<T>::left_padding_shifts[] = {31, 31, 0, 1, 0}; constexpr const char basic_data<T>::right_padding_shifts[];
template <typename T> #endif
const char basic_data<T>::right_padding_shifts[] = {0, 31, 0, 1, 0};
template <typename T> struct bits { template <typename T> struct bits {
static FMT_CONSTEXPR_DECL const int value = static FMT_CONSTEXPR_DECL const int value =

View File

@ -283,20 +283,13 @@ FMT_END_NAMESPACE
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
#if __cplusplus >= 202002L #if __cplusplus >= 202002L || \
(__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
# define FMT_CONSTEXPR20 constexpr # define FMT_CONSTEXPR20 constexpr
#else #else
# define FMT_CONSTEXPR20 inline # define FMT_CONSTEXPR20 inline
#endif #endif
constexpr bool is_constant_evaluated() FMT_DETECTED_NOEXCEPT {
#ifdef __cpp_lib_is_constant_evaluated
return std::is_constant_evaluated();
#else
return false;
#endif
}
// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't have // An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't have
// undefined behavior (e.g. due to type aliasing). // undefined behavior (e.g. due to type aliasing).
// Example: uint64_t d = bit_cast<uint64_t>(2.718); // Example: uint64_t d = bit_cast<uint64_t>(2.718);
@ -552,7 +545,7 @@ inline size_t count_code_points(basic_string_view<Char> s) {
} }
// Counts the number of code points in a UTF-8 string. // Counts the number of code points in a UTF-8 string.
inline size_t count_code_points(basic_string_view<char> s) { FMT_CONSTEXPR size_t count_code_points(basic_string_view<char> s) {
const char* data = s.data(); const char* data = s.data();
size_t num_code_points = 0; size_t num_code_points = 0;
for (size_t i = 0, size = s.size(); i != size; ++i) { for (size_t i = 0, size = s.size(); i != size; ++i) {
@ -585,12 +578,15 @@ inline size_t code_point_index(basic_string_view<char8_type> s, size_t n) {
// <algorithm> is spectacularly slow to compile in C++20 so use a simple fill_n // <algorithm> is spectacularly slow to compile in C++20 so use a simple fill_n
// instead (#1998). // instead (#1998).
template <typename OutputIt, typename Size, typename T> template <typename OutputIt, typename Size, typename T>
OutputIt fill_n(OutputIt out, Size count, const T& value) { FMT_CONSTEXPR OutputIt fill_n(OutputIt out, Size count, const T& value) {
for (Size i = 0; i < count; ++i) *out++ = value; for (Size i = 0; i < count; ++i) *out++ = value;
return out; return out;
} }
template <typename T, typename Size> template <typename T, typename Size>
inline T* fill_n(T* out, Size count, char value) { FMT_CONSTEXPR20 T* fill_n(T* out, Size count, char value) {
if (is_constant_evaluated()) {
return fill_n<T*, Size, T>(out, count, value);
}
std::memset(out, value, to_unsigned(count)); std::memset(out, value, to_unsigned(count));
return out + count; return out + count;
} }
@ -938,14 +934,14 @@ template <typename T = void> struct FMT_EXTERN_TEMPLATE_API basic_data {
// GCC generates slightly better code for pairs than chars. // GCC generates slightly better code for pairs than chars.
using digit_pair = char[2]; using digit_pair = char[2];
static const digit_pair digits[]; static const digit_pair digits[];
static const char hex_digits[]; static constexpr const char hex_digits[] = "0123456789abcdef";
static const char foreground_color[]; static const char foreground_color[];
static const char background_color[]; static const char background_color[];
static const char reset_color[5]; static const char reset_color[5];
static const wchar_t wreset_color[5]; static const wchar_t wreset_color[5];
static const char signs[]; static const char signs[];
static const char left_padding_shifts[5]; static constexpr const char left_padding_shifts[] = {31, 31, 0, 1, 0};
static const char right_padding_shifts[5]; static constexpr const char right_padding_shifts[] = {0, 31, 0, 1, 0};
// DEPRECATED! These are for ABI compatibility. // DEPRECATED! These are for ABI compatibility.
static const uint32_t zero_or_powers_of_10_32[]; static const uint32_t zero_or_powers_of_10_32[];
@ -1144,8 +1140,8 @@ inline format_decimal_result<Iterator> format_decimal(Iterator out, UInt value,
} }
template <unsigned BASE_BITS, typename Char, typename UInt> template <unsigned BASE_BITS, typename Char, typename UInt>
inline Char* format_uint(Char* buffer, UInt value, int num_digits, FMT_CONSTEXPR Char* format_uint(Char* buffer, UInt value, int num_digits,
bool upper = false) { bool upper = false) {
buffer += num_digits; buffer += num_digits;
Char* end = buffer; Char* end = buffer;
do { do {
@ -1224,8 +1220,8 @@ template <typename Char> struct fill_t {
size_ = static_cast<unsigned char>(size); size_ = static_cast<unsigned char>(size);
} }
size_t size() const { return size_; } constexpr size_t size() const { return size_; }
const Char* data() const { return data_; } constexpr const Char* data() const { return data_; }
FMT_CONSTEXPR Char& operator[](size_t index) { return data_[index]; } FMT_CONSTEXPR Char& operator[](size_t index) { return data_[index]; }
FMT_CONSTEXPR const Char& operator[](size_t index) const { FMT_CONSTEXPR const Char& operator[](size_t index) const {
@ -1544,7 +1540,8 @@ class cstring_type_checker : public ErrorHandler {
}; };
template <typename OutputIt, typename Char> template <typename OutputIt, typename Char>
FMT_NOINLINE OutputIt fill(OutputIt it, size_t n, const fill_t<Char>& fill) { FMT_NOINLINE FMT_CONSTEXPR OutputIt fill(OutputIt it, size_t n,
const fill_t<Char>& fill) {
auto fill_size = fill.size(); auto fill_size = fill.size();
if (fill_size == 1) return detail::fill_n(it, n, fill[0]); if (fill_size == 1) return detail::fill_n(it, n, fill[0]);
auto data = fill.data(); auto data = fill.data();
@ -1558,15 +1555,16 @@ FMT_NOINLINE OutputIt fill(OutputIt it, size_t n, const fill_t<Char>& fill) {
// width: output display width in (terminal) column positions. // width: output display width in (terminal) column positions.
template <align::type align = align::left, typename OutputIt, typename Char, template <align::type align = align::left, typename OutputIt, typename Char,
typename F> typename F>
inline OutputIt write_padded(OutputIt out, FMT_CONSTEXPR OutputIt write_padded(OutputIt out,
const basic_format_specs<Char>& specs, size_t size, const basic_format_specs<Char>& specs,
size_t width, F&& f) { size_t size, size_t width, F&& f) {
static_assert(align == align::left || align == align::right, ""); static_assert(align == align::left || align == align::right, "");
unsigned spec_width = to_unsigned(specs.width); unsigned spec_width = to_unsigned(specs.width);
size_t padding = spec_width > width ? spec_width - width : 0; size_t padding = spec_width > width ? spec_width - width : 0;
size_t left_padding = 0;
auto* shifts = align == align::left ? data::left_padding_shifts auto* shifts = align == align::left ? data::left_padding_shifts
: data::right_padding_shifts; : data::right_padding_shifts;
size_t left_padding = padding >> shifts[specs.align]; left_padding = padding >> shifts[specs.align];
auto it = reserve(out, size + padding * specs.fill.size()); auto it = reserve(out, size + padding * specs.fill.size());
it = fill(it, left_padding, specs.fill); it = fill(it, left_padding, specs.fill);
it = f(it); it = f(it);
@ -1576,9 +1574,9 @@ inline OutputIt write_padded(OutputIt out,
template <align::type align = align::left, typename OutputIt, typename Char, template <align::type align = align::left, typename OutputIt, typename Char,
typename F> typename F>
inline OutputIt write_padded(OutputIt out, constexpr OutputIt write_padded(OutputIt out,
const basic_format_specs<Char>& specs, size_t size, const basic_format_specs<Char>& specs,
F&& f) { size_t size, F&& f) {
return write_padded<align>(out, specs, size, size, f); return write_padded<align>(out, specs, size, size, f);
} }
@ -1598,8 +1596,8 @@ template <typename Char> struct write_int_data {
size_t size; size_t size;
size_t padding; size_t padding;
write_int_data(int num_digits, string_view prefix, FMT_CONSTEXPR write_int_data(int num_digits, string_view prefix,
const basic_format_specs<Char>& specs) const basic_format_specs<Char>& specs)
: size(prefix.size() + to_unsigned(num_digits)), padding(0) { : size(prefix.size() + to_unsigned(num_digits)), padding(0) {
if (specs.align == align::numeric) { if (specs.align == align::numeric) {
auto width = to_unsigned(specs.width); auto width = to_unsigned(specs.width);
@ -1618,8 +1616,9 @@ template <typename Char> struct write_int_data {
// <left-padding><prefix><numeric-padding><digits><right-padding> // <left-padding><prefix><numeric-padding><digits><right-padding>
// where <digits> are written by f(it). // where <digits> are written by f(it).
template <typename OutputIt, typename Char, typename F> template <typename OutputIt, typename Char, typename F>
OutputIt write_int(OutputIt out, int num_digits, string_view prefix, FMT_CONSTEXPR OutputIt write_int(OutputIt out, int num_digits,
const basic_format_specs<Char>& specs, F f) { string_view prefix,
const basic_format_specs<Char>& specs, F f) {
auto data = write_int_data<Char>(num_digits, prefix, specs); auto data = write_int_data<Char>(num_digits, prefix, specs);
using iterator = remove_reference_t<decltype(reserve(out, 0))>; using iterator = remove_reference_t<decltype(reserve(out, 0))>;
return write_padded<align::right>(out, specs, data.size, [=](iterator it) { return write_padded<align::right>(out, specs, data.size, [=](iterator it) {
@ -1631,8 +1630,8 @@ OutputIt write_int(OutputIt out, int num_digits, string_view prefix,
} }
template <typename StrChar, typename Char, typename OutputIt> template <typename StrChar, typename Char, typename OutputIt>
OutputIt write(OutputIt out, basic_string_view<StrChar> s, FMT_CONSTEXPR OutputIt write(OutputIt out, basic_string_view<StrChar> s,
const basic_format_specs<Char>& specs) { const basic_format_specs<Char>& specs) {
auto data = s.data(); auto data = s.data();
auto size = s.size(); auto size = s.size();
if (specs.precision >= 0 && to_unsigned(specs.precision) < size) if (specs.precision >= 0 && to_unsigned(specs.precision) < size)
@ -1658,11 +1657,13 @@ template <typename OutputIt, typename Char, typename UInt> struct int_writer {
using iterator = using iterator =
remove_reference_t<decltype(reserve(std::declval<OutputIt&>(), 0))>; remove_reference_t<decltype(reserve(std::declval<OutputIt&>(), 0))>;
string_view get_prefix() const { return string_view(prefix, prefix_size); } constexpr string_view get_prefix() const {
return string_view(prefix, prefix_size);
}
template <typename Int> template <typename Int>
int_writer(OutputIt output, locale_ref loc, Int value, FMT_CONSTEXPR int_writer(OutputIt output, locale_ref loc, Int value,
const basic_format_specs<Char>& s) const basic_format_specs<Char>& s)
: out(output), : out(output),
locale(loc), locale(loc),
specs(s), specs(s),
@ -1679,7 +1680,7 @@ template <typename OutputIt, typename Char, typename UInt> struct int_writer {
} }
} }
void on_dec() { FMT_CONSTEXPR void on_dec() {
auto num_digits = count_digits(abs_value); auto num_digits = count_digits(abs_value);
out = write_int( out = write_int(
out, num_digits, get_prefix(), specs, [this, num_digits](iterator it) { out, num_digits, get_prefix(), specs, [this, num_digits](iterator it) {
@ -1687,7 +1688,7 @@ template <typename OutputIt, typename Char, typename UInt> struct int_writer {
}); });
} }
void on_hex() { FMT_CONSTEXPR void on_hex() {
if (specs.alt) { if (specs.alt) {
prefix[prefix_size++] = '0'; prefix[prefix_size++] = '0';
prefix[prefix_size++] = specs.type; prefix[prefix_size++] = specs.type;
@ -1700,7 +1701,7 @@ template <typename OutputIt, typename Char, typename UInt> struct int_writer {
}); });
} }
void on_bin() { FMT_CONSTEXPR void on_bin() {
if (specs.alt) { if (specs.alt) {
prefix[prefix_size++] = '0'; prefix[prefix_size++] = '0';
prefix[prefix_size++] = static_cast<char>(specs.type); prefix[prefix_size++] = static_cast<char>(specs.type);
@ -1712,7 +1713,7 @@ template <typename OutputIt, typename Char, typename UInt> struct int_writer {
}); });
} }
void on_oct() { FMT_CONSTEXPR void on_oct() {
int num_digits = count_digits<3>(abs_value); int num_digits = count_digits<3>(abs_value);
if (specs.alt && specs.precision <= num_digits && abs_value != 0) { if (specs.alt && specs.precision <= num_digits && abs_value != 0) {
// Octal prefix '0' is counted as a digit, so only add it if precision // Octal prefix '0' is counted as a digit, so only add it if precision
@ -2041,8 +2042,8 @@ inline OutputIt write(OutputIt out, T value) {
} }
template <typename Char, typename OutputIt> template <typename Char, typename OutputIt>
OutputIt write_char(OutputIt out, Char value, constexpr OutputIt write_char(OutputIt out, Char value,
const basic_format_specs<Char>& specs) { const basic_format_specs<Char>& specs) {
using iterator = remove_reference_t<decltype(reserve(out, 0))>; using iterator = remove_reference_t<decltype(reserve(out, 0))>;
return write_padded(out, specs, 1, [=](iterator it) { return write_padded(out, specs, 1, [=](iterator it) {
*it++ = value; *it++ = value;
@ -2205,7 +2206,8 @@ class arg_formatter_base {
using reserve_iterator = remove_reference_t<decltype( using reserve_iterator = remove_reference_t<decltype(
detail::reserve(std::declval<iterator&>(), 0))>; detail::reserve(std::declval<iterator&>(), 0))>;
template <typename T> void write_int(T value, const format_specs& spec) { template <typename T>
FMT_CONSTEXPR void write_int(T value, const format_specs& spec) {
using uint_type = uint32_or_64_or_128_t<T>; using uint_type = uint32_or_64_or_128_t<T>;
int_writer<iterator, Char, uint_type> w(out_, locale_, value, spec); int_writer<iterator, Char, uint_type> w(out_, locale_, value, spec);
handle_int_type_spec(spec.type, w); handle_int_type_spec(spec.type, w);
@ -2243,7 +2245,8 @@ class arg_formatter_base {
} }
template <typename Ch> template <typename Ch>
void write(basic_string_view<Ch> s, const format_specs& specs = {}) { FMT_CONSTEXPR void write(basic_string_view<Ch> s,
const format_specs& specs = {}) {
out_ = detail::write(out_, s, specs); out_ = detail::write(out_, s, specs);
} }
@ -2255,14 +2258,14 @@ class arg_formatter_base {
arg_formatter_base& formatter; arg_formatter_base& formatter;
Char value; Char value;
char_spec_handler(arg_formatter_base& f, Char val) constexpr char_spec_handler(arg_formatter_base& f, Char val)
: formatter(f), value(val) {} : formatter(f), value(val) {}
void on_int() { FMT_CONSTEXPR void on_int() {
// char is only formatted as int if there are specs. // char is only formatted as int if there are specs.
formatter.write_int(static_cast<int>(value), *formatter.specs_); formatter.write_int(static_cast<int>(value), *formatter.specs_);
} }
void on_char() { FMT_CONSTEXPR void on_char() {
if (formatter.specs_) if (formatter.specs_)
formatter.out_ = write_char(formatter.out_, value, *formatter.specs_); formatter.out_ = write_char(formatter.out_, value, *formatter.specs_);
else else
@ -2285,7 +2288,7 @@ class arg_formatter_base {
iterator out() { return out_; } iterator out() { return out_; }
format_specs* specs() { return specs_; } format_specs* specs() { return specs_; }
void write(bool value) { FMT_CONSTEXPR void write(bool value) {
if (specs_) if (specs_)
write(string_view(value ? "true" : "false"), *specs_); write(string_view(value ? "true" : "false"), *specs_);
else else
@ -2303,7 +2306,7 @@ class arg_formatter_base {
} }
public: public:
arg_formatter_base(OutputIt out, format_specs* s, locale_ref loc) constexpr arg_formatter_base(OutputIt out, format_specs* s, locale_ref loc)
: out_(out), locale_(loc), specs_(s) {} : out_(out), locale_(loc), specs_(s) {}
iterator operator()(monostate) { iterator operator()(monostate) {
@ -2312,7 +2315,7 @@ class arg_formatter_base {
} }
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(is_integral<T>::value)>
FMT_INLINE iterator operator()(T value) { FMT_CONSTEXPR iterator operator()(T value) {
if (specs_) if (specs_)
write_int(value, *specs_); write_int(value, *specs_);
else else
@ -2320,13 +2323,13 @@ class arg_formatter_base {
return out_; return out_;
} }
iterator operator()(Char value) { FMT_CONSTEXPR iterator operator()(Char value) {
handle_char_specs(specs_, handle_char_specs(specs_,
char_spec_handler(*this, static_cast<Char>(value))); char_spec_handler(*this, static_cast<Char>(value)));
return out_; return out_;
} }
iterator operator()(bool value) { FMT_CONSTEXPR iterator operator()(bool value) {
if (specs_ && specs_->type) return (*this)(value ? 1 : 0); if (specs_ && specs_->type) return (*this)(value ? 1 : 0);
write(value != 0); write(value != 0);
return out_; return out_;
@ -2348,7 +2351,7 @@ class arg_formatter_base {
return out_; return out_;
} }
iterator operator()(basic_string_view<Char> value) { FMT_CONSTEXPR iterator operator()(basic_string_view<Char> value) {
if (specs_) { if (specs_) {
check_string_type_spec(specs_->type, error_handler()); check_string_type_spec(specs_->type, error_handler());
write(value, *specs_); write(value, *specs_);
@ -2388,7 +2391,7 @@ class arg_formatter : public arg_formatter_base<OutputIt, Char> {
*specs* contains format specifier information for standard argument types. *specs* contains format specifier information for standard argument types.
\endrst \endrst
*/ */
explicit arg_formatter( constexpr explicit arg_formatter(
context_type& ctx, context_type& ctx,
basic_format_parse_context<char_type>* parse_ctx = nullptr, basic_format_parse_context<char_type>* parse_ctx = nullptr,
format_specs* specs = nullptr, const Char* ptr = nullptr) format_specs* specs = nullptr, const Char* ptr = nullptr)
@ -3296,8 +3299,9 @@ void check_format_string(S format_str) {
} }
template <template <typename> class Handler, typename Context> template <template <typename> class Handler, typename Context>
void handle_dynamic_spec(int& value, arg_ref<typename Context::char_type> ref, FMT_CONSTEXPR void handle_dynamic_spec(int& value,
Context& ctx) { arg_ref<typename Context::char_type> ref,
Context& ctx) {
switch (ref.kind) { switch (ref.kind) {
case arg_id_kind::none: case arg_id_kind::none:
break; break;
@ -3529,14 +3533,16 @@ struct formatter<T, Char,
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) { FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const
detail::handle_dynamic_spec<detail::width_checker>(specs_.width, -> decltype(ctx.out()) {
specs_.width_ref, ctx); auto specs = specs_;
detail::handle_dynamic_spec<detail::width_checker>(specs.width,
specs.width_ref, ctx);
detail::handle_dynamic_spec<detail::precision_checker>( detail::handle_dynamic_spec<detail::precision_checker>(
specs_.precision, specs_.precision_ref, ctx); specs.precision, specs.precision_ref, ctx);
using af = detail::arg_formatter<typename FormatContext::iterator, using af = detail::arg_formatter<typename FormatContext::iterator,
typename FormatContext::char_type>; typename FormatContext::char_type>;
return visit_format_arg(af(ctx, nullptr, &specs_), return visit_format_arg(af(ctx, nullptr, &specs),
detail::make_arg<FormatContext>(val)); detail::make_arg<FormatContext>(val));
} }
@ -3544,13 +3550,14 @@ struct formatter<T, Char,
detail::dynamic_format_specs<Char> specs_; detail::dynamic_format_specs<Char> specs_;
}; };
#define FMT_FORMAT_AS(Type, Base) \ #define FMT_FORMAT_AS(Type, Base) \
template <typename Char> \ template <typename Char> \
struct formatter<Type, Char> : formatter<Base, Char> { \ struct formatter<Type, Char> : formatter<Base, Char> { \
template <typename FormatContext> \ template <typename FormatContext> \
auto format(Type const& val, FormatContext& ctx) -> decltype(ctx.out()) { \ auto format(Type const& val, FormatContext& ctx) const \
return formatter<Base, Char>::format(static_cast<Base>(val), ctx); \ -> decltype(ctx.out()) { \
} \ return formatter<Base, Char>::format(static_cast<Base>(val), ctx); \
} \
} }
FMT_FORMAT_AS(signed char, int); FMT_FORMAT_AS(signed char, int);
@ -3570,7 +3577,7 @@ FMT_FORMAT_AS(std::byte, unsigned);
template <typename Char> template <typename Char>
struct formatter<void*, Char> : formatter<const void*, Char> { struct formatter<void*, Char> : formatter<const void*, Char> {
template <typename FormatContext> template <typename FormatContext>
auto format(void* val, FormatContext& ctx) -> decltype(ctx.out()) { auto format(void* val, FormatContext& ctx) const -> decltype(ctx.out()) {
return formatter<const void*, Char>::format(val, ctx); return formatter<const void*, Char>::format(val, ctx);
} }
}; };
@ -3578,7 +3585,8 @@ struct formatter<void*, Char> : formatter<const void*, Char> {
template <typename Char, size_t N> template <typename Char, size_t N>
struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> { struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {
template <typename FormatContext> template <typename FormatContext>
auto format(const Char* val, FormatContext& ctx) -> decltype(ctx.out()) { FMT_CONSTEXPR auto format(const Char* val, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<basic_string_view<Char>, Char>::format(val, ctx); return formatter<basic_string_view<Char>, Char>::format(val, ctx);
} }
}; };

View File

@ -7,9 +7,6 @@
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#if __cplusplus >= 202002L
# include <string_view>
#endif
// Check that fmt/compile.h compiles with windows.h included before it. // Check that fmt/compile.h compiles with windows.h included before it.
#ifdef _WIN32 #ifdef _WIN32
@ -187,17 +184,18 @@ TEST(CompileTest, CompileFormatStringLiteral) {
} }
#endif #endif
#if __cplusplus >= 202002L #if __cplusplus >= 202002L || \
template <size_t max_string_length> struct test_string { (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept { template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
return (std::string_view(rhs).compare(buffer.data()) == 0); return fmt::basic_string_view<Char>(rhs).compare(buffer.data()) == 0;
} }
std::array<char, max_string_length> buffer{}; std::array<Char, max_string_length> buffer{};
}; };
template <size_t max_string_length, typename... Args> template <size_t max_string_length, typename Char = char, typename... Args>
consteval auto test_format(auto format, const Args&... args) { consteval auto test_format(auto format, const Args&... args) {
test_string<max_string_length> string{}; test_string<max_string_length, Char> string{};
fmt::format_to(string.buffer.data(), format, args...); fmt::format_to(string.buffer.data(), format, args...);
return string; return string;
} }
@ -205,6 +203,8 @@ consteval auto test_format(auto format, const Args&... args) {
TEST(CompileTimeFormattingTest, Bool) { TEST(CompileTimeFormattingTest, Bool) {
EXPECT_EQ("true", test_format<5>(FMT_COMPILE("{}"), true)); EXPECT_EQ("true", test_format<5>(FMT_COMPILE("{}"), true));
EXPECT_EQ("false", test_format<6>(FMT_COMPILE("{}"), false)); EXPECT_EQ("false", test_format<6>(FMT_COMPILE("{}"), false));
EXPECT_EQ("true ", test_format<6>(FMT_COMPILE("{:5}"), true));
EXPECT_EQ("1", test_format<2>(FMT_COMPILE("{:d}"), true));
} }
TEST(CompileTimeFormattingTest, Integer) { TEST(CompileTimeFormattingTest, Integer) {
@ -213,16 +213,54 @@ TEST(CompileTimeFormattingTest, Integer) {
EXPECT_EQ("42 42", test_format<6>(FMT_COMPILE("{} {}"), 42, 42)); EXPECT_EQ("42 42", test_format<6>(FMT_COMPILE("{} {}"), 42, 42));
EXPECT_EQ("42 42", EXPECT_EQ("42 42",
test_format<6>(FMT_COMPILE("{} {}"), uint32_t{42}, uint64_t{42})); test_format<6>(FMT_COMPILE("{} {}"), uint32_t{42}, uint64_t{42}));
EXPECT_EQ("+42", test_format<4>(FMT_COMPILE("{:+}"), 42));
EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{:-}"), 42));
EXPECT_EQ(" 42", test_format<4>(FMT_COMPILE("{: }"), 42));
EXPECT_EQ("-0042", test_format<6>(FMT_COMPILE("{:05}"), -42));
EXPECT_EQ("101010", test_format<7>(FMT_COMPILE("{:b}"), 42));
EXPECT_EQ("0b101010", test_format<9>(FMT_COMPILE("{:#b}"), 42));
EXPECT_EQ("0B101010", test_format<9>(FMT_COMPILE("{:#B}"), 42));
EXPECT_EQ("042", test_format<4>(FMT_COMPILE("{:#o}"), 042));
EXPECT_EQ("0x4a", test_format<5>(FMT_COMPILE("{:#x}"), 0x4a));
EXPECT_EQ("0X4A", test_format<5>(FMT_COMPILE("{:#X}"), 0x4a));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ll));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ull));
EXPECT_EQ("42 ", test_format<5>(FMT_COMPILE("{:<4}"), 42));
EXPECT_EQ(" 42", test_format<5>(FMT_COMPILE("{:>4}"), 42));
EXPECT_EQ(" 42 ", test_format<5>(FMT_COMPILE("{:^4}"), 42));
EXPECT_EQ("**-42", test_format<6>(FMT_COMPILE("{:*>5}"), -42));
}
TEST(CompileTimeFormattingTest, Char) {
EXPECT_EQ("c", test_format<2>(FMT_COMPILE("{}"), 'c'));
EXPECT_EQ("c ", test_format<4>(FMT_COMPILE("{:3}"), 'c'));
EXPECT_EQ("99", test_format<3>(FMT_COMPILE("{:d}"), 'c'));
} }
TEST(CompileTimeFormattingTest, String) { TEST(CompileTimeFormattingTest, String) {
EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), "42")); EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), "42"));
EXPECT_EQ("The answer is 42", EXPECT_EQ("The answer is 42",
test_format<17>(FMT_COMPILE("{} is {}"), "The answer", "42")); test_format<17>(FMT_COMPILE("{} is {}"), "The answer", "42"));
EXPECT_EQ("abc**", test_format<6>(FMT_COMPILE("{:*<5}"), "abc"));
EXPECT_EQ("**🤡**", test_format<9>(FMT_COMPILE("{:*^5}"), "🤡"));
} }
TEST(CompileTimeFormattingTest, Combination) { TEST(CompileTimeFormattingTest, Combination) {
EXPECT_EQ("420, true, answer", EXPECT_EQ("420, true, answer",
test_format<18>(FMT_COMPILE("{}, {}, {}"), 420, true, "answer")); test_format<18>(FMT_COMPILE("{}, {}, {}"), 420, true, "answer"));
EXPECT_EQ(" -42", test_format<5>(FMT_COMPILE("{:{}}"), -42, 4));
}
TEST(CompileTimeFormattingTest, MultiByteFill) {
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
} }
#endif #endif