Add opt out for built-in types

This commit is contained in:
Victor Zverovich
2024-08-25 09:20:40 -07:00
parent 5a0a37340c
commit 377cf203e3
11 changed files with 149 additions and 81 deletions

View File

@@ -291,6 +291,16 @@
# define FMT_UNICODE 1
#endif
// Specifies whether to handle built-in and string types specially.
// FMT_BUILTIN_TYPE=0 may result in smaller library size at the cost of higher
// per-call binary size.
#ifndef FMT_BUILTIN_TYPES
# define FMT_BUILTIN_TYPES 1
#endif
#if !FMT_BUILTIN_TYPES && !defined(__cpp_if_constexpr)
# error FMT_BUILTIN_TYPES=0 requires constexpr if support
#endif
// Check if rtti is available.
#ifndef FMT_USE_RTTI
// __RTTI is for EDG compilers. _CPPRTTI is for MSVC.
@@ -1317,9 +1327,19 @@ template <typename Char> struct named_arg_info {
template <typename T> struct is_named_arg : std::false_type {};
template <typename T> struct is_statically_named_arg : std::false_type {};
template <typename T, typename Char>
template <typename Char, typename T>
struct is_named_arg<named_arg<Char, T>> : std::true_type {};
template <typename Char, typename T>
auto unwrap_named_arg(const named_arg<Char, T>& arg) -> const T& {
return arg.value;
}
template <typename T,
FMT_ENABLE_IF(!is_named_arg<remove_reference_t<T>>::value)>
auto unwrap_named_arg(T&& value) -> T&& {
return value;
}
template <bool B = false> constexpr auto count() -> size_t { return B ? 1 : 0; }
template <bool B1, bool B2, bool... Tail> constexpr auto count() -> size_t {
return (B1 ? 1 : 0) + count<B2, Tail...>();
@@ -1354,6 +1374,8 @@ template <typename Context> struct custom_value {
void (*format)(void* arg, parse_context& parse_ctx, Context& ctx);
};
enum class custom_tag {};
// A formatting argument value.
template <typename Context> class value {
public:
@@ -1400,24 +1422,26 @@ template <typename Context> class value {
string.size = val.size();
}
FMT_ALWAYS_INLINE value(const void* val) : pointer(val) {}
FMT_ALWAYS_INLINE value(const named_arg_info<char_type>* args, size_t size)
: named_args{args, size} {}
template <typename T> FMT_CONSTEXPR20 FMT_ALWAYS_INLINE value(T& val) {
using value_type = remove_const_t<T>;
template <typename T>
FMT_CONSTEXPR20 FMT_ALWAYS_INLINE value(T& val, custom_tag = {}) {
using value_type = typename std::remove_cv<T>::type;
// T may overload operator& e.g. std::vector<bool>::reference in libc++.
#if defined(__cpp_if_constexpr)
if constexpr (std::is_same<decltype(&val), T*>::value)
custom.value = const_cast<value_type*>(&val);
#endif
if (!is_constant_evaluated())
custom.value = const_cast<char*>(&reinterpret_cast<const char&>(val));
custom.value =
const_cast<char*>(&reinterpret_cast<const volatile char&>(val));
// Get the formatter type through the context to allow different contexts
// have different extension points, e.g. `formatter<T>` for `format` and
// `printf_formatter<T>` for `printf`.
custom.format = format_custom_arg<
value_type, typename Context::template formatter_type<value_type>>;
}
FMT_ALWAYS_INLINE value(const named_arg_info<char_type>* args, size_t size)
: named_args{args, size} {}
value(unformattable);
value(unformattable_char);
value(unformattable_pointer);
@@ -1606,6 +1630,12 @@ using mapped_type_constant =
type_constant<decltype(arg_mapper<Context>().map(std::declval<const T&>())),
typename Context::char_type>;
template <typename T, typename Context,
type TYPE = mapped_type_constant<T, Context>::value>
using stored_type_constant = std::integral_constant<
type, Context::builtin_types || TYPE == type::int_type ? TYPE
: type::custom_type>;
enum { packed_arg_bits = 4 };
// Maximum number of arguments with packed types.
enum { max_packed_args = 62 / packed_arg_bits };
@@ -1643,7 +1673,7 @@ template <typename> constexpr auto encode_types() -> unsigned long long {
template <typename Context, typename Arg, typename... Args>
constexpr auto encode_types() -> unsigned long long {
return static_cast<unsigned>(mapped_type_constant<Arg, Context>::value) |
return static_cast<unsigned>(stored_type_constant<Arg, Context>::value) |
(encode_types<Context, Args...>() << packed_arg_bits);
}
@@ -1686,6 +1716,8 @@ FMT_CONSTEXPR auto make_arg(T& val) -> value<Context> {
#if defined(__cpp_if_constexpr)
if constexpr (!formattable)
type_is_unformattable_for<T, typename Context::char_type> _;
if constexpr (!Context::builtin_types && !std::is_same<arg_type, int>::value)
return {unwrap_named_arg(val), custom_tag()};
#endif
static_assert(
formattable,
@@ -1697,7 +1729,7 @@ FMT_CONSTEXPR auto make_arg(T& val) -> value<Context> {
template <typename Context, typename T>
FMT_CONSTEXPR auto make_arg(T& val) -> basic_format_arg<Context> {
auto arg = basic_format_arg<Context>();
arg.type_ = mapped_type_constant<T, Context>::value;
arg.type_ = stored_type_constant<T, Context>::value;
arg.value_ = make_arg<true, Context>(val);
return arg;
}
@@ -1781,6 +1813,7 @@ template <typename Context> class basic_format_arg {
friend class basic_format_args<Context>;
friend class dynamic_format_arg_store<Context>;
friend class loc_value;
using char_type = typename Context::char_type;
@@ -1999,6 +2032,7 @@ class context {
using format_arg = basic_format_arg<context>;
using parse_context_type = basic_format_parse_context<char>;
template <typename T> using formatter_type = formatter<T, char>;
enum { builtin_types = FMT_BUILTIN_TYPES };
/// Constructs a `basic_format_context` object. References to the arguments
/// are stored in the object so make sure they have appropriate lifetimes.

View File

@@ -1037,6 +1037,7 @@ template <typename OutputIt, typename Char> class generic_context {
using iterator = OutputIt;
using parse_context_type = basic_format_parse_context<Char>;
template <typename T> using formatter_type = formatter<T, Char>;
enum { builtin_types = FMT_BUILTIN_TYPES };
constexpr generic_context(OutputIt out,
basic_format_args<generic_context> ctx_args,
@@ -1074,7 +1075,10 @@ class loc_value {
public:
template <typename T, FMT_ENABLE_IF(!detail::is_float128<T>::value)>
loc_value(T value) : value_(detail::make_arg<format_context>(value)) {}
loc_value(T value) {
value_.type_ = detail::mapped_type_constant<T, format_context>::value;
value_.value_ = detail::arg_mapper<format_context>().map(value);
}
template <typename T, FMT_ENABLE_IF(detail::is_float128<T>::value)>
loc_value(T) {}
@@ -3672,6 +3676,10 @@ FMT_CONSTEXPR auto write(OutputIt out, const T& value)
return formatter.format(value, ctx);
}
template <typename T>
using is_builtin =
bool_constant<std::is_same<T, int>::value || FMT_BUILTIN_TYPES>;
// An argument visitor that formats the argument and writes it via the output
// iterator. It's a class and not a generic lambda for compatibility with C++11.
template <typename Char> struct default_arg_formatter {
@@ -3681,7 +3689,15 @@ template <typename Char> struct default_arg_formatter {
void operator()(monostate) { report_error("argument not found"); }
template <typename T> void operator()(T value) { write<Char>(out, value); }
template <typename T, FMT_ENABLE_IF(is_builtin<T>::value)>
void operator()(T value) {
write<Char>(out, value);
}
template <typename T, FMT_ENABLE_IF(!is_builtin<T>::value)>
void operator()(T) {
FMT_ASSERT(false, "");
}
void operator()(typename basic_format_arg<context>::handle h) {
// Use a null locale since the default format must be unlocalized.
@@ -3699,10 +3715,17 @@ template <typename Char> struct arg_formatter {
const format_specs& specs;
locale_ref locale;
template <typename T>
template <typename T, FMT_ENABLE_IF(is_builtin<T>::value)>
FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator {
return detail::write<Char>(out, value, specs, locale);
}
template <typename T, FMT_ENABLE_IF(!is_builtin<T>::value)>
auto operator()(T) -> iterator {
FMT_ASSERT(false, "");
return out;
}
auto operator()(typename basic_format_arg<context>::handle) -> iterator {
// User-defined types are handled separately because they require access
// to the parse context.
@@ -3939,17 +3962,17 @@ FMT_FORMAT_AS(unsigned short, unsigned);
FMT_FORMAT_AS(long, detail::long_type);
FMT_FORMAT_AS(unsigned long, detail::ulong_type);
FMT_FORMAT_AS(Char*, const Char*);
FMT_FORMAT_AS(std::nullptr_t, const void*);
FMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>);
FMT_FORMAT_AS(std::nullptr_t, const void*);
FMT_FORMAT_AS(void*, const void*);
template <typename Char, size_t N>
struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {};
template <typename Char, typename Traits, typename Allocator>
class formatter<std::basic_string<Char, Traits, Allocator>, Char>
: public formatter<basic_string_view<Char>, Char> {};
template <typename Char, size_t N>
struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {};
template <typename T, typename Char>
struct formatter<T, Char,
enable_if_t<(detail::bitint_traits<T>::is_formattable)>>
@@ -4208,8 +4231,6 @@ template <typename Char> struct format_handler {
specs.precision_ref, context);
}
if (begin == end || *begin != '}')
report_error("missing '}' in format string");
arg.visit(arg_formatter<Char>{context.out(), specs, context.locale()});
return begin;
}
@@ -4246,7 +4267,7 @@ FMT_END_EXPORT
template <typename T, typename Char, type TYPE>
template <typename FormatContext>
FMT_CONSTEXPR FMT_INLINE auto native_formatter<T, Char, TYPE>::format(
FMT_CONSTEXPR auto native_formatter<T, Char, TYPE>::format(
const T& val, FormatContext& ctx) const -> decltype(ctx.out()) {
if (!specs_.dynamic())
return write<Char>(ctx.out(), val, specs_, ctx.locale());

View File

@@ -35,6 +35,7 @@ template <typename Char> class basic_printf_context {
using char_type = Char;
using parse_context_type = basic_format_parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
enum { builtin_types = 1 };
/// Constructs a `printf_context` object. References to the arguments are
/// stored in the context object so make sure they have appropriate lifetimes.
@@ -238,19 +239,23 @@ class printf_arg_formatter : public arg_formatter<Char> {
write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
}
template <typename T> void write(T value) {
detail::write<Char>(this->out, value, this->specs, this->locale);
}
public:
printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {}
void operator()(monostate value) { base::operator()(value); }
void operator()(monostate value) { write(value); }
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
void operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (!std::is_same<T, Char>::value) {
base::operator()(value);
write(value);
return;
}
format_specs s = this->specs;
@@ -265,33 +270,33 @@ class printf_arg_formatter : public arg_formatter<Char> {
// ignored for non-numeric types
if (s.align() == align::none || s.align() == align::numeric)
s.set_align(align::right);
write<Char>(this->out, static_cast<Char>(value), s);
detail::write<Char>(this->out, static_cast<Char>(value), s);
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
void operator()(T value) {
base::operator()(value);
write(value);
}
void operator()(const char* value) {
if (value)
base::operator()(value);
write(value);
else
write_null_pointer(this->specs.type() != presentation_type::pointer);
}
void operator()(const wchar_t* value) {
if (value)
base::operator()(value);
write(value);
else
write_null_pointer(this->specs.type() != presentation_type::pointer);
}
void operator()(basic_string_view<Char> value) { base::operator()(value); }
void operator()(basic_string_view<Char> value) { write(value); }
void operator()(const void* value) {
if (value)
base::operator()(value);
write(value);
else
write_null_pointer();
}