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
19 changes: 10 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,8 @@ 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_CONSTEXPR14 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,20 @@
# define FMT_CONSTEXPR_DECL
#endif

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

#if __cplusplus >= 202002L
# define FMT_CONSTEXPR20 constexpr
# define FMT_IS_CONSTANT_EVALUATED std::is_constant_evaluated()
alexezeder marked this conversation as resolved.
Show resolved Hide resolved
#else
# define FMT_CONSTEXPR20
alexezeder marked this conversation as resolved.
Show resolved Hide resolved
# define FMT_IS_CONSTANT_EVALUATED false
#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
19 changes: 1 addition & 18 deletions include/fmt/format-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,24 +229,7 @@ template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
}

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'}};
const data_digit_pair basic_data<T>::digits[] = FMT_DATA_DIGITS;

template <typename T>
const char basic_data<T>::hex_digits[] = "0123456789abcdef";
Expand Down
130 changes: 98 additions & 32 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,41 @@ FMT_END_NAMESPACE
# define FMT_DEPRECATED_NUMERIC_ALIGN 0
#endif

#define FMT_DATA_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' \
} \
}
alexezeder marked this conversation as resolved.
Show resolved Hide resolved

FMT_BEGIN_NAMESPACE
namespace detail {

using data_digit_pair = char[2];

namespace compile_time_formatting_data {
constexpr data_digit_pair digits[] = FMT_DATA_DIGITS;
}
alexezeder marked this conversation as resolved.
Show resolved Hide resolved

// 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 +422,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 +447,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 +620,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_CONSTEXPR14 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 (FMT_IS_CONSTANT_EVALUATED) {
return copy_str<OutChar, InputIt, OutChar*>(begin, end, out);
}
return std::uninitialized_copy(begin, end, out);
}

Expand Down Expand Up @@ -943,17 +980,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> inline FMT_CONSTEXPR14 int count_digits_trivial(T n) {
int count = 1;
for (;;) {
// Integer division is slow so do it for a group of four digits instead
Expand All @@ -967,10 +994,27 @@ inline int count_digits(uint64_t n) {
count += 4;
}
}

#if defined(FMT_BUILTIN_CLZLL)
alexezeder marked this conversation as resolved.
Show resolved Hide resolved
// 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 (FMT_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.
inline FMT_CONSTEXPR14 int count_digits(uint64_t n) {
return count_digits_trivial(n);
}
#endif

#if FMT_USE_INT128
inline int count_digits(uint128_t n) {
inline FMT_CONSTEXPR14 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 +1031,8 @@ 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>
inline FMT_CONSTEXPR14 int count_digits(UInt n) {
int num_digits = 0;
do {
++num_digits;
Expand All @@ -1005,9 +1050,12 @@ template <> int count_digits<4>(detail::fallback_uintptr n);
# define FMT_ALWAYS_INLINE inline
#endif

#ifdef FMT_BUILTIN_CLZ
#if defined(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 (FMT_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 +1092,30 @@ 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 (FMT_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_CONSTEXPR14 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 (FMT_IS_CONSTANT_EVALUATED) {
copy2<char>(dst, src);
return;
}
memcpy(dst, src, 2);
}

template <typename Iterator> struct format_decimal_result {
Iterator begin;
Expand All @@ -1067,25 +1126,32 @@ 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");
const data_digit_pair* digits;
if (FMT_IS_CONSTANT_EVALUATED) {
digits = compile_time_formatting_data::digits;
} else {
digits = data::digits;
}
out += size;
Char* end = out;
while (value >= 100) {
// Integer division is slow so do it for a group of two digits instead
// 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, 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, digits[value]);
return {out, end};
}

Expand Down Expand Up @@ -2049,7 +2115,7 @@ OutputIt write(OutputIt out, string_view value) {
}

template <typename Char, typename OutputIt>
OutputIt write(OutputIt out, basic_string_view<Char> value) {
FMT_CONSTEXPR14 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 +2132,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_CONSTEXPR14 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 +2151,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_CONSTEXPR14 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_CONSTEXPR14 OutputIt write(OutputIt out, const Char* value) {
if (!value) {
FMT_THROW(format_error("string pointer is null"));
} else {
Expand Down
Loading