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

This commit is contained in:
Alexey Ochapov
2020-12-25 06:40:03 -08:00
committed by GitHub
parent a750bf3ac6
commit bbd6ed5bc5
5 changed files with 182 additions and 121 deletions
+78 -70
View File
@@ -283,20 +283,13 @@ FMT_END_NAMESPACE
FMT_BEGIN_NAMESPACE
namespace detail {
#if __cplusplus >= 202002L
#if __cplusplus >= 202002L || \
(__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
# define FMT_CONSTEXPR20 constexpr
#else
# define FMT_CONSTEXPR20 inline
#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
// undefined behavior (e.g. due to type aliasing).
// 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.
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();
size_t num_code_points = 0;
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
// instead (#1998).
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;
return out;
}
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));
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.
using digit_pair = char[2];
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 background_color[];
static const char reset_color[5];
static const wchar_t wreset_color[5];
static const char signs[];
static const char left_padding_shifts[5];
static const char right_padding_shifts[5];
static constexpr const char left_padding_shifts[] = {31, 31, 0, 1, 0};
static constexpr const char right_padding_shifts[] = {0, 31, 0, 1, 0};
// DEPRECATED! These are for ABI compatibility.
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>
inline Char* format_uint(Char* buffer, UInt value, int num_digits,
bool upper = false) {
FMT_CONSTEXPR Char* format_uint(Char* buffer, UInt value, int num_digits,
bool upper = false) {
buffer += num_digits;
Char* end = buffer;
do {
@@ -1224,8 +1220,8 @@ template <typename Char> struct fill_t {
size_ = static_cast<unsigned char>(size);
}
size_t size() const { return size_; }
const Char* data() const { return data_; }
constexpr size_t size() const { return size_; }
constexpr const Char* data() const { return data_; }
FMT_CONSTEXPR Char& operator[](size_t index) { return data_[index]; }
FMT_CONSTEXPR const Char& operator[](size_t index) const {
@@ -1544,7 +1540,8 @@ class cstring_type_checker : public ErrorHandler {
};
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();
if (fill_size == 1) return detail::fill_n(it, n, fill[0]);
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.
template <align::type align = align::left, typename OutputIt, typename Char,
typename F>
inline OutputIt write_padded(OutputIt out,
const basic_format_specs<Char>& specs, size_t size,
size_t width, F&& f) {
FMT_CONSTEXPR OutputIt write_padded(OutputIt out,
const basic_format_specs<Char>& specs,
size_t size, size_t width, F&& f) {
static_assert(align == align::left || align == align::right, "");
unsigned spec_width = to_unsigned(specs.width);
size_t padding = spec_width > width ? spec_width - width : 0;
size_t left_padding = 0;
auto* shifts = align == align::left ? data::left_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());
it = fill(it, left_padding, specs.fill);
it = f(it);
@@ -1576,9 +1574,9 @@ inline OutputIt write_padded(OutputIt out,
template <align::type align = align::left, typename OutputIt, typename Char,
typename F>
inline OutputIt write_padded(OutputIt out,
const basic_format_specs<Char>& specs, size_t size,
F&& f) {
constexpr OutputIt write_padded(OutputIt out,
const basic_format_specs<Char>& specs,
size_t size, F&& 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 padding;
write_int_data(int num_digits, string_view prefix,
const basic_format_specs<Char>& specs)
FMT_CONSTEXPR write_int_data(int num_digits, string_view prefix,
const basic_format_specs<Char>& specs)
: size(prefix.size() + to_unsigned(num_digits)), padding(0) {
if (specs.align == align::numeric) {
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>
// where <digits> are written by f(it).
template <typename OutputIt, typename Char, typename F>
OutputIt write_int(OutputIt out, int num_digits, string_view prefix,
const basic_format_specs<Char>& specs, F f) {
FMT_CONSTEXPR OutputIt write_int(OutputIt out, int num_digits,
string_view prefix,
const basic_format_specs<Char>& specs, F f) {
auto data = write_int_data<Char>(num_digits, prefix, specs);
using iterator = remove_reference_t<decltype(reserve(out, 0))>;
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>
OutputIt write(OutputIt out, basic_string_view<StrChar> s,
const basic_format_specs<Char>& specs) {
FMT_CONSTEXPR OutputIt write(OutputIt out, basic_string_view<StrChar> s,
const basic_format_specs<Char>& specs) {
auto data = s.data();
auto size = s.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 =
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>
int_writer(OutputIt output, locale_ref loc, Int value,
const basic_format_specs<Char>& s)
FMT_CONSTEXPR int_writer(OutputIt output, locale_ref loc, Int value,
const basic_format_specs<Char>& s)
: out(output),
locale(loc),
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);
out = write_int(
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) {
prefix[prefix_size++] = '0';
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) {
prefix[prefix_size++] = '0';
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);
if (specs.alt && specs.precision <= num_digits && abs_value != 0) {
// 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>
OutputIt write_char(OutputIt out, Char value,
const basic_format_specs<Char>& specs) {
constexpr OutputIt write_char(OutputIt out, Char value,
const basic_format_specs<Char>& specs) {
using iterator = remove_reference_t<decltype(reserve(out, 0))>;
return write_padded(out, specs, 1, [=](iterator it) {
*it++ = value;
@@ -2205,7 +2206,8 @@ class arg_formatter_base {
using reserve_iterator = remove_reference_t<decltype(
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>;
int_writer<iterator, Char, uint_type> w(out_, locale_, value, spec);
handle_int_type_spec(spec.type, w);
@@ -2243,7 +2245,8 @@ class arg_formatter_base {
}
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);
}
@@ -2255,14 +2258,14 @@ class arg_formatter_base {
arg_formatter_base& formatter;
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) {}
void on_int() {
FMT_CONSTEXPR void on_int() {
// char is only formatted as int if there are specs.
formatter.write_int(static_cast<int>(value), *formatter.specs_);
}
void on_char() {
FMT_CONSTEXPR void on_char() {
if (formatter.specs_)
formatter.out_ = write_char(formatter.out_, value, *formatter.specs_);
else
@@ -2285,7 +2288,7 @@ class arg_formatter_base {
iterator out() { return out_; }
format_specs* specs() { return specs_; }
void write(bool value) {
FMT_CONSTEXPR void write(bool value) {
if (specs_)
write(string_view(value ? "true" : "false"), *specs_);
else
@@ -2303,7 +2306,7 @@ class arg_formatter_base {
}
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) {}
iterator operator()(monostate) {
@@ -2312,7 +2315,7 @@ class arg_formatter_base {
}
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)>
FMT_INLINE iterator operator()(T value) {
FMT_CONSTEXPR iterator operator()(T value) {
if (specs_)
write_int(value, *specs_);
else
@@ -2320,13 +2323,13 @@ class arg_formatter_base {
return out_;
}
iterator operator()(Char value) {
FMT_CONSTEXPR iterator operator()(Char value) {
handle_char_specs(specs_,
char_spec_handler(*this, static_cast<Char>(value)));
return out_;
}
iterator operator()(bool value) {
FMT_CONSTEXPR iterator operator()(bool value) {
if (specs_ && specs_->type) return (*this)(value ? 1 : 0);
write(value != 0);
return out_;
@@ -2348,7 +2351,7 @@ class arg_formatter_base {
return out_;
}
iterator operator()(basic_string_view<Char> value) {
FMT_CONSTEXPR iterator operator()(basic_string_view<Char> value) {
if (specs_) {
check_string_type_spec(specs_->type, error_handler());
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.
\endrst
*/
explicit arg_formatter(
constexpr explicit arg_formatter(
context_type& ctx,
basic_format_parse_context<char_type>* parse_ctx = 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>
void handle_dynamic_spec(int& value, arg_ref<typename Context::char_type> ref,
Context& ctx) {
FMT_CONSTEXPR void handle_dynamic_spec(int& value,
arg_ref<typename Context::char_type> ref,
Context& ctx) {
switch (ref.kind) {
case arg_id_kind::none:
break;
@@ -3529,14 +3533,16 @@ struct formatter<T, Char,
}
template <typename FormatContext>
auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
detail::handle_dynamic_spec<detail::width_checker>(specs_.width,
specs_.width_ref, ctx);
FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto specs = specs_;
detail::handle_dynamic_spec<detail::width_checker>(specs.width,
specs.width_ref, ctx);
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,
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));
}
@@ -3544,13 +3550,14 @@ struct formatter<T, Char,
detail::dynamic_format_specs<Char> specs_;
};
#define FMT_FORMAT_AS(Type, Base) \
template <typename Char> \
struct formatter<Type, Char> : formatter<Base, Char> { \
template <typename FormatContext> \
auto format(Type const& val, FormatContext& ctx) -> decltype(ctx.out()) { \
return formatter<Base, Char>::format(static_cast<Base>(val), ctx); \
} \
#define FMT_FORMAT_AS(Type, Base) \
template <typename Char> \
struct formatter<Type, Char> : formatter<Base, Char> { \
template <typename FormatContext> \
auto format(Type const& val, FormatContext& ctx) const \
-> decltype(ctx.out()) { \
return formatter<Base, Char>::format(static_cast<Base>(val), ctx); \
} \
}
FMT_FORMAT_AS(signed char, int);
@@ -3570,7 +3577,7 @@ FMT_FORMAT_AS(std::byte, unsigned);
template <typename Char>
struct formatter<void*, Char> : formatter<const void*, Char> {
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);
}
};
@@ -3578,7 +3585,8 @@ struct formatter<void*, Char> : formatter<const void*, Char> {
template <typename Char, size_t N>
struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {
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);
}
};