Basics of formatting at compile-time based on compile-time API (#2019)
This commit is contained in:
parent
119f7dc3d6
commit
dac753b81e
@ -394,7 +394,7 @@ template <typename Char> struct text {
|
|||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
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);
|
return write<Char>(out, data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -413,7 +413,7 @@ template <typename Char> struct code_unit {
|
|||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
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);
|
return write<Char>(out, value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -426,7 +426,7 @@ template <typename Char, typename T, int N> struct field {
|
|||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
|
|
||||||
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...);
|
||||||
return write<Char>(out, arg);
|
return write<Char>(out, arg);
|
||||||
@ -461,7 +461,7 @@ template <typename L, typename R> struct concat {
|
|||||||
using char_type = typename L::char_type;
|
using char_type = typename L::char_type;
|
||||||
|
|
||||||
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 {
|
||||||
out = lhs.format(out, args...);
|
out = lhs.format(out, args...);
|
||||||
return rhs.format(out, args...);
|
return rhs.format(out, args...);
|
||||||
}
|
}
|
||||||
@ -617,8 +617,8 @@ FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
|||||||
|
|
||||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
constexpr OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||||
const Args&... args) {
|
const Args&... args) {
|
||||||
return cf.format(out, args...);
|
return cf.format(out, args...);
|
||||||
}
|
}
|
||||||
# endif // __cpp_if_constexpr
|
# endif // __cpp_if_constexpr
|
||||||
@ -654,8 +654,8 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
|||||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||||
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
|
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
|
||||||
CompiledFormat>::value)>
|
CompiledFormat>::value)>
|
||||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
constexpr OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||||
const Args&... args) {
|
const Args&... args) {
|
||||||
using char_type = typename CompiledFormat::char_type;
|
using char_type = typename CompiledFormat::char_type;
|
||||||
using context = format_context_t<OutputIt, char_type>;
|
using context = format_context_t<OutputIt, char_type>;
|
||||||
return detail::cf::vformat_to<context>(out, cf,
|
return detail::cf::vformat_to<context>(out, cf,
|
||||||
@ -664,7 +664,7 @@ OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
|||||||
|
|
||||||
template <typename OutputIt, typename S, typename... Args,
|
template <typename OutputIt, typename S, typename... Args,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
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());
|
constexpr auto compiled = detail::compile<Args...>(S());
|
||||||
return format_to(out, compiled, args...);
|
return format_to(out, compiled, args...);
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,12 @@
|
|||||||
# define FMT_CONSTEXPR_DECL
|
# define FMT_CONSTEXPR_DECL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if __cplusplus >= 202002L
|
||||||
|
# define FMT_CONSTEXPR20 constexpr
|
||||||
|
#else
|
||||||
|
# define FMT_CONSTEXPR20 inline
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef FMT_OVERRIDE
|
#ifndef FMT_OVERRIDE
|
||||||
# if FMT_HAS_FEATURE(cxx_override_control) || \
|
# if FMT_HAS_FEATURE(cxx_override_control) || \
|
||||||
(FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
|
(FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
|
||||||
@ -283,6 +289,14 @@ struct monostate {};
|
|||||||
|
|
||||||
namespace detail {
|
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.
|
// 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; }
|
||||||
|
|
||||||
|
@ -390,7 +390,7 @@ inline buffer_appender<T> reserve(buffer_appender<T> it, size_t n) {
|
|||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Iterator> inline Iterator& reserve(Iterator& it, size_t) {
|
template <typename Iterator> constexpr Iterator& reserve(Iterator& it, size_t) {
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +414,7 @@ inline std::back_insert_iterator<Container> base_iterator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Iterator>
|
template <typename Iterator>
|
||||||
inline Iterator base_iterator(Iterator, Iterator it) {
|
constexpr Iterator base_iterator(Iterator, Iterator it) {
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,14 +587,17 @@ using needs_conversion = bool_constant<
|
|||||||
|
|
||||||
template <typename OutChar, typename InputIt, typename OutputIt,
|
template <typename OutChar, typename InputIt, typename OutputIt,
|
||||||
FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)>
|
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++;
|
while (begin != end) *it++ = *begin++;
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutChar, typename InputIt,
|
template <typename OutChar, typename InputIt,
|
||||||
FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)>
|
FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)>
|
||||||
inline OutChar* copy_str(InputIt begin, InputIt end, OutChar* out) {
|
FMT_CONSTEXPR20 OutChar* copy_str(InputIt begin, InputIt end, OutChar* out) {
|
||||||
|
if (is_constant_evaluated()) {
|
||||||
|
return copy_str<OutChar, InputIt, OutChar*>(begin, end, out);
|
||||||
|
}
|
||||||
return std::uninitialized_copy(begin, end, out);
|
return std::uninitialized_copy(begin, end, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -951,17 +954,7 @@ FMT_EXTERN template struct basic_data<void>;
|
|||||||
// This is a struct rather than an alias to avoid shadowing warnings in gcc.
|
// This is a struct rather than an alias to avoid shadowing warnings in gcc.
|
||||||
struct data : basic_data<> {};
|
struct data : basic_data<> {};
|
||||||
|
|
||||||
#ifdef FMT_BUILTIN_CLZLL
|
template <typename T> FMT_CONSTEXPR int count_digits_fallback(T n) {
|
||||||
// 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) {
|
|
||||||
int count = 1;
|
int count = 1;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// Integer division is slow so do it for a group of four digits instead
|
// Integer division is slow so do it for a group of four digits instead
|
||||||
@ -975,10 +968,25 @@ inline int count_digits(uint64_t n) {
|
|||||||
count += 4;
|
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.
|
||||||
|
FMT_CONSTEXPR20 int count_digits(uint64_t n) {
|
||||||
|
if (is_constant_evaluated()) {
|
||||||
|
return count_digits_fallback(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_fallback(n); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if FMT_USE_INT128
|
#if FMT_USE_INT128
|
||||||
inline int count_digits(uint128_t n) {
|
FMT_CONSTEXPR int count_digits(uint128_t n) {
|
||||||
int count = 1;
|
int count = 1;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// Integer division is slow so do it for a group of four digits instead
|
// Integer division is slow so do it for a group of four digits instead
|
||||||
@ -995,7 +1003,7 @@ inline int count_digits(uint128_t n) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Counts the number of digits in n. BITS = log2(radix).
|
// 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;
|
int num_digits = 0;
|
||||||
do {
|
do {
|
||||||
++num_digits;
|
++num_digits;
|
||||||
@ -1015,7 +1023,10 @@ template <> int count_digits<4>(detail::fallback_uintptr n);
|
|||||||
|
|
||||||
#ifdef FMT_BUILTIN_CLZ
|
#ifdef FMT_BUILTIN_CLZ
|
||||||
// Optional version of count_digits for better performance on 32-bit platforms.
|
// Optional version of count_digits for better performance on 32-bit platforms.
|
||||||
inline int count_digits(uint32_t n) {
|
FMT_CONSTEXPR20 int count_digits(uint32_t n) {
|
||||||
|
if (is_constant_evaluated()) {
|
||||||
|
return count_digits_fallback(n);
|
||||||
|
}
|
||||||
auto t = bsr2log10(FMT_BUILTIN_CLZ(n | 1) ^ 31);
|
auto t = bsr2log10(FMT_BUILTIN_CLZ(n | 1) ^ 31);
|
||||||
return t - (n < data::zero_or_powers_of_10_32_new[t]);
|
return t - (n < data::zero_or_powers_of_10_32_new[t]);
|
||||||
}
|
}
|
||||||
@ -1075,11 +1086,20 @@ template <typename Iterator> struct format_decimal_result {
|
|||||||
// buffer of specified size. The caller must ensure that the buffer is large
|
// buffer of specified size. The caller must ensure that the buffer is large
|
||||||
// enough.
|
// enough.
|
||||||
template <typename Char, typename UInt>
|
template <typename Char, typename UInt>
|
||||||
inline format_decimal_result<Char*> format_decimal(Char* out, UInt value,
|
FMT_CONSTEXPR20 format_decimal_result<Char*> format_decimal(Char* out,
|
||||||
int size) {
|
UInt value,
|
||||||
|
int size) {
|
||||||
FMT_ASSERT(size >= count_digits(value), "invalid digit count");
|
FMT_ASSERT(size >= count_digits(value), "invalid digit count");
|
||||||
out += size;
|
out += size;
|
||||||
Char* end = out;
|
Char* end = out;
|
||||||
|
if (is_constant_evaluated()) {
|
||||||
|
while (value >= 10) {
|
||||||
|
*--out = static_cast<Char>('0' + value % 10);
|
||||||
|
value /= 10;
|
||||||
|
}
|
||||||
|
*--out = static_cast<Char>('0' + value);
|
||||||
|
return {out, end};
|
||||||
|
}
|
||||||
while (value >= 100) {
|
while (value >= 100) {
|
||||||
// Integer division is slow so do it for a group of two digits instead
|
// 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
|
// of for every digit. The idea comes from the talk by Alexandrescu
|
||||||
@ -2048,7 +2068,7 @@ OutputIt write(OutputIt out, string_view value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename OutputIt>
|
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());
|
auto it = reserve(out, value.size());
|
||||||
it = copy_str<Char>(value.begin(), value.end(), it);
|
it = copy_str<Char>(value.begin(), value.end(), it);
|
||||||
return base_iterator(out, it);
|
return base_iterator(out, it);
|
||||||
@ -2058,7 +2078,7 @@ template <typename Char, typename OutputIt, typename T,
|
|||||||
FMT_ENABLE_IF(is_integral<T>::value &&
|
FMT_ENABLE_IF(is_integral<T>::value &&
|
||||||
!std::is_same<T, bool>::value &&
|
!std::is_same<T, bool>::value &&
|
||||||
!std::is_same<T, Char>::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);
|
auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||||
bool negative = is_negative(value);
|
bool negative = is_negative(value);
|
||||||
// Don't do -abs_value since it trips unsigned-integer-overflow sanitizer.
|
// Don't do -abs_value since it trips unsigned-integer-overflow sanitizer.
|
||||||
@ -2077,19 +2097,19 @@ OutputIt write(OutputIt out, T value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename OutputIt>
|
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"));
|
return write<Char>(out, string_view(value ? "true" : "false"));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename OutputIt>
|
template <typename Char, typename OutputIt>
|
||||||
OutputIt write(OutputIt out, Char value) {
|
FMT_CONSTEXPR OutputIt write(OutputIt out, Char value) {
|
||||||
auto it = reserve(out, 1);
|
auto it = reserve(out, 1);
|
||||||
*it++ = value;
|
*it++ = value;
|
||||||
return base_iterator(out, it);
|
return base_iterator(out, it);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename OutputIt>
|
template <typename Char, typename OutputIt>
|
||||||
OutputIt write(OutputIt out, const Char* value) {
|
FMT_CONSTEXPR OutputIt write(OutputIt out, const Char* value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
FMT_THROW(format_error("string pointer is null"));
|
FMT_THROW(format_error("string pointer is null"));
|
||||||
} else {
|
} else {
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
|
|
||||||
#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
|
||||||
@ -171,3 +174,69 @@ TEST(CompileTest, TextAndArg) {
|
|||||||
EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42));
|
EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if __cplusplus >= 202002L
|
||||||
|
template <size_t max_string_length> struct test_string {
|
||||||
|
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
||||||
|
return (std::string_view(rhs).compare(buffer.data()) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<char, max_string_length> buffer{};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <size_t max_string_length, typename... Args>
|
||||||
|
consteval auto test_format(auto format, const Args&... args) {
|
||||||
|
test_string<max_string_length> string{};
|
||||||
|
fmt::format_to(string.buffer.data(), format, args...);
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CompileTimeFormattingTest, Bool) {
|
||||||
|
{
|
||||||
|
constexpr auto result = test_format<5>(FMT_COMPILE("{}"), true);
|
||||||
|
EXPECT_EQ(result, "true");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
constexpr auto result = test_format<6>(FMT_COMPILE("{}"), false);
|
||||||
|
EXPECT_EQ(result, "false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CompileTimeFormattingTest, Integer) {
|
||||||
|
{
|
||||||
|
constexpr auto result = test_format<3>(FMT_COMPILE("{}"), 42);
|
||||||
|
EXPECT_EQ(result, "42");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
constexpr auto result = test_format<4>(FMT_COMPILE("{}"), 420);
|
||||||
|
EXPECT_EQ(result, "420");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
constexpr auto result = test_format<6>(FMT_COMPILE("{} {}"), 42, 42);
|
||||||
|
EXPECT_EQ(result, "42 42");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
constexpr auto result =
|
||||||
|
test_format<6>(FMT_COMPILE("{} {}"), uint32_t{42}, uint64_t{42});
|
||||||
|
EXPECT_EQ(result, "42 42");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CompileTimeFormattingTest, String) {
|
||||||
|
{
|
||||||
|
constexpr auto result = test_format<3>(FMT_COMPILE("{}"), "42");
|
||||||
|
EXPECT_EQ(result, "42");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
constexpr auto result =
|
||||||
|
test_format<17>(FMT_COMPILE("{} is {}"), "The answer", "42");
|
||||||
|
EXPECT_EQ(result, "The answer is 42");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CompileTimeFormattingTest, Combination) {
|
||||||
|
constexpr auto result =
|
||||||
|
test_format<18>(FMT_COMPILE("{}, {}, {}"), 420, true, "answer");
|
||||||
|
EXPECT_EQ(result, "420, true, answer");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user