Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basics of formatting at compile-time based on compile-time API #2019

Merged
merged 13 commits into from
Nov 29, 2020
Merged
18 changes: 9 additions & 9 deletions include/fmt/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ template <typename Char> struct text {
using char_type = Char;

template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&...) const {
constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, data);
}
};
Expand All @@ -413,7 +413,7 @@ template <typename Char> struct code_unit {
using char_type = Char;

template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&...) const {
constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, value);
}
};
Expand All @@ -426,7 +426,7 @@ template <typename Char, typename T, int N> struct field {
using char_type = Char;

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&`.
const T& arg = get<N>(args...);
return write<Char>(out, arg);
Expand Down Expand Up @@ -461,7 +461,7 @@ template <typename L, typename R> struct concat {
using char_type = typename L::char_type;

template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const {
constexpr OutputIt format(OutputIt out, const Args&... args) const {
out = lhs.format(out, args...);
return rhs.format(out, args...);
}
Expand Down Expand Up @@ -617,8 +617,8 @@ FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,

template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
constexpr OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
return cf.format(out, args...);
}
# endif // __cpp_if_constexpr
Expand Down Expand Up @@ -654,8 +654,8 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
constexpr OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
using char_type = typename CompiledFormat::char_type;
using context = format_context_t<OutputIt, char_type>;
return detail::cf::vformat_to<context>(out, cf,
Expand All @@ -664,7 +664,7 @@ OutputIt format_to(OutputIt out, const CompiledFormat& cf,

template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
OutputIt format_to(OutputIt out, const S&, const Args&... args) {
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, const Args&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
return format_to(out, compiled, args...);
}
Expand Down
14 changes: 14 additions & 0 deletions include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@
# define FMT_CONSTEXPR_DECL
#endif

#if __cplusplus >= 202002L
# define FMT_CONSTEXPR20 constexpr
#else
# define FMT_CONSTEXPR20
alexezeder marked this conversation as resolved.
Show resolved Hide resolved
#endif

#ifndef FMT_OVERRIDE
# if FMT_HAS_FEATURE(cxx_override_control) || \
(FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
Expand Down Expand Up @@ -279,6 +285,14 @@ struct monostate {};

namespace detail {

constexpr bool is_constant_evaluated() FMT_DETECTED_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.
template <typename T> constexpr T const_check(T value) { return value; }

Expand Down
20 changes: 0 additions & 20 deletions include/fmt/format-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,26 +228,6 @@ template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1;
}

template <typename T>
const typename basic_data<T>::digit_pair basic_data<T>::digits[] = {
{'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
{'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
{'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
{'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
{'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
{'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
{'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
{'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
{'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
{'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
{'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
{'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
{'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
{'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
{'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
{'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
{'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};

template <typename T>
const char basic_data<T>::hex_digits[] = "0123456789abcdef";

Expand Down
117 changes: 81 additions & 36 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,30 @@ FMT_END_NAMESPACE
FMT_BEGIN_NAMESPACE
namespace detail {

// GCC generates slightly better code for pairs than chars.
using data_digit_pair = char[2];

namespace formatting_data {
constexpr data_digit_pair digits[] = {
{'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
{'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
{'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
{'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
{'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
{'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
{'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
{'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
{'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
{'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
{'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
{'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
{'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
{'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
{'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
{'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
{'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
}

// 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);
Expand Down Expand Up @@ -390,7 +414,8 @@ inline buffer_appender<T> reserve(buffer_appender<T> it, size_t n) {
return it;
}

template <typename Iterator> inline Iterator& reserve(Iterator& it, size_t) {
template <typename Iterator>
inline constexpr Iterator& reserve(Iterator& it, size_t) {
alexezeder marked this conversation as resolved.
Show resolved Hide resolved
alexezeder marked this conversation as resolved.
Show resolved Hide resolved
return it;
}

Expand All @@ -414,7 +439,7 @@ inline std::back_insert_iterator<Container> base_iterator(
}

template <typename Iterator>
inline Iterator base_iterator(Iterator, Iterator it) {
inline constexpr Iterator base_iterator(Iterator, Iterator it) {
return it;
}

Expand Down Expand Up @@ -587,14 +612,18 @@ using needs_conversion = bool_constant<

template <typename OutChar, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)>
OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) {
FMT_CONSTEXPR OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) {
while (begin != end) *it++ = *begin++;
return it;
}

template <typename OutChar, typename InputIt,
FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)>
inline OutChar* copy_str(InputIt begin, InputIt end, OutChar* out) {
inline FMT_CONSTEXPR20 OutChar* copy_str(InputIt begin, InputIt end,
alexezeder marked this conversation as resolved.
Show resolved Hide resolved
OutChar* out) {
if (is_constant_evaluated()) {
return copy_str<OutChar, InputIt, OutChar*>(begin, end, out);
}
return std::uninitialized_copy(begin, end, out);
}

Expand Down Expand Up @@ -908,9 +937,6 @@ template <typename T = void> struct FMT_EXTERN_TEMPLATE_API basic_data {
static const uint64_t powers_of_5_64[];
static const uint32_t dragonbox_pow10_recovery_errors[];
#endif
// GCC generates slightly better code for pairs than chars.
using digit_pair = char[2];
static const digit_pair digits[];
alexezeder marked this conversation as resolved.
Show resolved Hide resolved
static const char hex_digits[];
static const char foreground_color[];
static const char background_color[];
Expand Down Expand Up @@ -943,17 +969,7 @@ FMT_EXTERN template struct basic_data<void>;
// This is a struct rather than an alias to avoid shadowing warnings in gcc.
struct data : basic_data<> {};

#ifdef FMT_BUILTIN_CLZLL
// Returns the number of decimal digits in n. Leading zeros are not counted
// except for n == 0 in which case count_digits returns 1.
inline int count_digits(uint64_t n) {
// https://github.com/fmtlib/format-benchmark/blob/master/digits10
auto t = bsr2log10(FMT_BUILTIN_CLZLL(n | 1) ^ 63);
return t - (n < data::zero_or_powers_of_10_64_new[t]);
}
#else
// Fallback version of count_digits used when __builtin_clz is not available.
inline int count_digits(uint64_t n) {
template <typename T> FMT_CONSTEXPR int count_digits_trivial(T n) {
alexezeder marked this conversation as resolved.
Show resolved Hide resolved
int count = 1;
for (;;) {
// Integer division is slow so do it for a group of four digits instead
Expand All @@ -967,10 +983,25 @@ inline int count_digits(uint64_t n) {
count += 4;
}
}

#ifdef FMT_BUILTIN_CLZLL
// Returns the number of decimal digits in n. Leading zeros are not counted
// except for n == 0 in which case count_digits returns 1.
inline FMT_CONSTEXPR20 int count_digits(uint64_t n) {
if (is_constant_evaluated()) {
return count_digits_trivial(n);
}
// https://github.com/fmtlib/format-benchmark/blob/master/digits10
auto t = bsr2log10(FMT_BUILTIN_CLZLL(n | 1) ^ 63);
return t - (n < data::zero_or_powers_of_10_64_new[t]);
}
#else
// Fallback version of count_digits used when __builtin_clz is not available.
FMT_CONSTEXPR int count_digits(uint64_t n) { return count_digits_trivial(n); }
#endif

#if FMT_USE_INT128
inline int count_digits(uint128_t n) {
FMT_CONSTEXPR int count_digits(uint128_t n) {
int count = 1;
for (;;) {
// Integer division is slow so do it for a group of four digits instead
Expand All @@ -987,7 +1018,7 @@ inline int count_digits(uint128_t n) {
#endif

// Counts the number of digits in n. BITS = log2(radix).
template <unsigned BITS, typename UInt> inline int count_digits(UInt n) {
template <unsigned BITS, typename UInt> FMT_CONSTEXPR int count_digits(UInt n) {
int num_digits = 0;
do {
++num_digits;
Expand All @@ -1007,7 +1038,10 @@ template <> int count_digits<4>(detail::fallback_uintptr n);

#ifdef FMT_BUILTIN_CLZ
// Optional version of count_digits for better performance on 32-bit platforms.
inline int count_digits(uint32_t n) {
inline FMT_CONSTEXPR20 int count_digits(uint32_t n) {
if (is_constant_evaluated()) {
return count_digits_trivial(n);
}
auto t = bsr2log10(FMT_BUILTIN_CLZ(n | 1) ^ 31);
return t - (n < data::zero_or_powers_of_10_32_new[t]);
}
Expand Down Expand Up @@ -1044,19 +1078,29 @@ template <> inline wchar_t decimal_point(locale_ref loc) {
}

// Compares two characters for equality.
template <typename Char> bool equal2(const Char* lhs, const char* rhs) {
template <typename Char>
constexpr bool equal2(const Char* lhs, const char* rhs) {
return lhs[0] == rhs[0] && lhs[1] == rhs[1];
}
inline bool equal2(const char* lhs, const char* rhs) {
inline FMT_CONSTEXPR20 bool equal2(const char* lhs, const char* rhs) {
if (is_constant_evaluated()) {
return equal2<char>(lhs, rhs);
}
return memcmp(lhs, rhs, 2) == 0;
}

// Copies two characters from src to dst.
template <typename Char> void copy2(Char* dst, const char* src) {
template <typename Char> FMT_CONSTEXPR void copy2(Char* dst, const char* src) {
*dst++ = static_cast<Char>(*src++);
*dst = static_cast<Char>(*src);
}
FMT_INLINE void copy2(char* dst, const char* src) { memcpy(dst, src, 2); }
FMT_INLINE FMT_CONSTEXPR20 void copy2(char* dst, const char* src) {
if (is_constant_evaluated()) {
copy2<char>(dst, src);
return;
}
memcpy(dst, src, 2);
}

template <typename Iterator> struct format_decimal_result {
Iterator begin;
Expand All @@ -1067,8 +1111,9 @@ template <typename Iterator> struct format_decimal_result {
// buffer of specified size. The caller must ensure that the buffer is large
// enough.
template <typename Char, typename UInt>
inline format_decimal_result<Char*> format_decimal(Char* out, UInt value,
int size) {
inline FMT_CONSTEXPR20 format_decimal_result<Char*> format_decimal(Char* out,
UInt value,
int size) {
FMT_ASSERT(size >= count_digits(value), "invalid digit count");
out += size;
Char* end = out;
Expand All @@ -1077,15 +1122,15 @@ inline format_decimal_result<Char*> format_decimal(Char* out, UInt value,
// of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison.
out -= 2;
copy2(out, data::digits[value % 100]);
copy2(out, formatting_data::digits[value % 100]);
value /= 100;
}
if (value < 10) {
*--out = static_cast<Char>('0' + value);
return {out, end};
}
out -= 2;
copy2(out, data::digits[value]);
copy2(out, formatting_data::digits[value]);
return {out, end};
}

Expand Down Expand Up @@ -1334,12 +1379,12 @@ template <typename Char, typename It> It write_exponent(int exp, It it) {
*it++ = static_cast<Char>('+');
}
if (exp >= 100) {
const char* top = data::digits[exp / 100];
const char* top = formatting_data::digits[exp / 100];
if (exp >= 1000) *it++ = static_cast<Char>(top[0]);
*it++ = static_cast<Char>(top[1]);
exp %= 100;
}
const char* d = data::digits[exp];
const char* d = formatting_data::digits[exp];
*it++ = static_cast<Char>(d[0]);
*it++ = static_cast<Char>(d[1]);
return it;
Expand Down Expand Up @@ -2049,7 +2094,7 @@ OutputIt write(OutputIt out, string_view value) {
}

template <typename Char, typename OutputIt>
OutputIt write(OutputIt out, basic_string_view<Char> value) {
FMT_CONSTEXPR OutputIt write(OutputIt out, basic_string_view<Char> value) {
auto it = reserve(out, value.size());
it = copy_str<Char>(value.begin(), value.end(), it);
return base_iterator(out, it);
Expand All @@ -2066,7 +2111,7 @@ template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(is_integral<T>::value &&
!std::is_same<T, bool>::value &&
!std::is_same<T, Char>::value)>
OutputIt write(OutputIt out, T value) {
FMT_CONSTEXPR OutputIt write(OutputIt out, T value) {
auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value);
bool negative = is_negative(value);
// Don't do -abs_value since it trips unsigned-integer-overflow sanitizer.
Expand All @@ -2085,19 +2130,19 @@ OutputIt write(OutputIt out, T value) {
}

template <typename Char, typename OutputIt>
OutputIt write(OutputIt out, bool value) {
constexpr OutputIt write(OutputIt out, bool value) {
return write<Char>(out, string_view(value ? "true" : "false"));
}

template <typename Char, typename OutputIt>
OutputIt write(OutputIt out, Char value) {
FMT_CONSTEXPR OutputIt write(OutputIt out, Char value) {
auto it = reserve(out, 1);
*it++ = value;
return base_iterator(out, it);
}

template <typename Char, typename OutputIt>
OutputIt write(OutputIt out, const Char* value) {
FMT_CONSTEXPR OutputIt write(OutputIt out, const Char* value) {
if (!value) {
FMT_THROW(format_error("string pointer is null"));
} else {
Expand Down
Loading