Skip to content

Commit 377cf20

Browse files
committed
Add opt out for built-in types
1 parent 5a0a373 commit 377cf20

File tree

11 files changed

+149
-81
lines changed

11 files changed

+149
-81
lines changed

include/fmt/base.h

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,16 @@
291291
# define FMT_UNICODE 1
292292
#endif
293293

294+
// Specifies whether to handle built-in and string types specially.
295+
// FMT_BUILTIN_TYPE=0 may result in smaller library size at the cost of higher
296+
// per-call binary size.
297+
#ifndef FMT_BUILTIN_TYPES
298+
# define FMT_BUILTIN_TYPES 1
299+
#endif
300+
#if !FMT_BUILTIN_TYPES && !defined(__cpp_if_constexpr)
301+
# error FMT_BUILTIN_TYPES=0 requires constexpr if support
302+
#endif
303+
294304
// Check if rtti is available.
295305
#ifndef FMT_USE_RTTI
296306
// __RTTI is for EDG compilers. _CPPRTTI is for MSVC.
@@ -1317,9 +1327,19 @@ template <typename Char> struct named_arg_info {
13171327
template <typename T> struct is_named_arg : std::false_type {};
13181328
template <typename T> struct is_statically_named_arg : std::false_type {};
13191329

1320-
template <typename T, typename Char>
1330+
template <typename Char, typename T>
13211331
struct is_named_arg<named_arg<Char, T>> : std::true_type {};
13221332

1333+
template <typename Char, typename T>
1334+
auto unwrap_named_arg(const named_arg<Char, T>& arg) -> const T& {
1335+
return arg.value;
1336+
}
1337+
template <typename T,
1338+
FMT_ENABLE_IF(!is_named_arg<remove_reference_t<T>>::value)>
1339+
auto unwrap_named_arg(T&& value) -> T&& {
1340+
return value;
1341+
}
1342+
13231343
template <bool B = false> constexpr auto count() -> size_t { return B ? 1 : 0; }
13241344
template <bool B1, bool B2, bool... Tail> constexpr auto count() -> size_t {
13251345
return (B1 ? 1 : 0) + count<B2, Tail...>();
@@ -1354,6 +1374,8 @@ template <typename Context> struct custom_value {
13541374
void (*format)(void* arg, parse_context& parse_ctx, Context& ctx);
13551375
};
13561376

1377+
enum class custom_tag {};
1378+
13571379
// A formatting argument value.
13581380
template <typename Context> class value {
13591381
public:
@@ -1400,24 +1422,26 @@ template <typename Context> class value {
14001422
string.size = val.size();
14011423
}
14021424
FMT_ALWAYS_INLINE value(const void* val) : pointer(val) {}
1403-
FMT_ALWAYS_INLINE value(const named_arg_info<char_type>* args, size_t size)
1404-
: named_args{args, size} {}
14051425

1406-
template <typename T> FMT_CONSTEXPR20 FMT_ALWAYS_INLINE value(T& val) {
1407-
using value_type = remove_const_t<T>;
1426+
template <typename T>
1427+
FMT_CONSTEXPR20 FMT_ALWAYS_INLINE value(T& val, custom_tag = {}) {
1428+
using value_type = typename std::remove_cv<T>::type;
14081429
// T may overload operator& e.g. std::vector<bool>::reference in libc++.
14091430
#if defined(__cpp_if_constexpr)
14101431
if constexpr (std::is_same<decltype(&val), T*>::value)
14111432
custom.value = const_cast<value_type*>(&val);
14121433
#endif
14131434
if (!is_constant_evaluated())
1414-
custom.value = const_cast<char*>(&reinterpret_cast<const char&>(val));
1435+
custom.value =
1436+
const_cast<char*>(&reinterpret_cast<const volatile char&>(val));
14151437
// Get the formatter type through the context to allow different contexts
14161438
// have different extension points, e.g. `formatter<T>` for `format` and
14171439
// `printf_formatter<T>` for `printf`.
14181440
custom.format = format_custom_arg<
14191441
value_type, typename Context::template formatter_type<value_type>>;
14201442
}
1443+
FMT_ALWAYS_INLINE value(const named_arg_info<char_type>* args, size_t size)
1444+
: named_args{args, size} {}
14211445
value(unformattable);
14221446
value(unformattable_char);
14231447
value(unformattable_pointer);
@@ -1606,6 +1630,12 @@ using mapped_type_constant =
16061630
type_constant<decltype(arg_mapper<Context>().map(std::declval<const T&>())),
16071631
typename Context::char_type>;
16081632

1633+
template <typename T, typename Context,
1634+
type TYPE = mapped_type_constant<T, Context>::value>
1635+
using stored_type_constant = std::integral_constant<
1636+
type, Context::builtin_types || TYPE == type::int_type ? TYPE
1637+
: type::custom_type>;
1638+
16091639
enum { packed_arg_bits = 4 };
16101640
// Maximum number of arguments with packed types.
16111641
enum { max_packed_args = 62 / packed_arg_bits };
@@ -1643,7 +1673,7 @@ template <typename> constexpr auto encode_types() -> unsigned long long {
16431673

16441674
template <typename Context, typename Arg, typename... Args>
16451675
constexpr auto encode_types() -> unsigned long long {
1646-
return static_cast<unsigned>(mapped_type_constant<Arg, Context>::value) |
1676+
return static_cast<unsigned>(stored_type_constant<Arg, Context>::value) |
16471677
(encode_types<Context, Args...>() << packed_arg_bits);
16481678
}
16491679

@@ -1686,6 +1716,8 @@ FMT_CONSTEXPR auto make_arg(T& val) -> value<Context> {
16861716
#if defined(__cpp_if_constexpr)
16871717
if constexpr (!formattable)
16881718
type_is_unformattable_for<T, typename Context::char_type> _;
1719+
if constexpr (!Context::builtin_types && !std::is_same<arg_type, int>::value)
1720+
return {unwrap_named_arg(val), custom_tag()};
16891721
#endif
16901722
static_assert(
16911723
formattable,
@@ -1697,7 +1729,7 @@ FMT_CONSTEXPR auto make_arg(T& val) -> value<Context> {
16971729
template <typename Context, typename T>
16981730
FMT_CONSTEXPR auto make_arg(T& val) -> basic_format_arg<Context> {
16991731
auto arg = basic_format_arg<Context>();
1700-
arg.type_ = mapped_type_constant<T, Context>::value;
1732+
arg.type_ = stored_type_constant<T, Context>::value;
17011733
arg.value_ = make_arg<true, Context>(val);
17021734
return arg;
17031735
}
@@ -1781,6 +1813,7 @@ template <typename Context> class basic_format_arg {
17811813

17821814
friend class basic_format_args<Context>;
17831815
friend class dynamic_format_arg_store<Context>;
1816+
friend class loc_value;
17841817

17851818
using char_type = typename Context::char_type;
17861819

@@ -1999,6 +2032,7 @@ class context {
19992032
using format_arg = basic_format_arg<context>;
20002033
using parse_context_type = basic_format_parse_context<char>;
20012034
template <typename T> using formatter_type = formatter<T, char>;
2035+
enum { builtin_types = FMT_BUILTIN_TYPES };
20022036

20032037
/// Constructs a `basic_format_context` object. References to the arguments
20042038
/// are stored in the object so make sure they have appropriate lifetimes.

include/fmt/format.h

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,7 @@ template <typename OutputIt, typename Char> class generic_context {
10371037
using iterator = OutputIt;
10381038
using parse_context_type = basic_format_parse_context<Char>;
10391039
template <typename T> using formatter_type = formatter<T, Char>;
1040+
enum { builtin_types = FMT_BUILTIN_TYPES };
10401041

10411042
constexpr generic_context(OutputIt out,
10421043
basic_format_args<generic_context> ctx_args,
@@ -1074,7 +1075,10 @@ class loc_value {
10741075

10751076
public:
10761077
template <typename T, FMT_ENABLE_IF(!detail::is_float128<T>::value)>
1077-
loc_value(T value) : value_(detail::make_arg<format_context>(value)) {}
1078+
loc_value(T value) {
1079+
value_.type_ = detail::mapped_type_constant<T, format_context>::value;
1080+
value_.value_ = detail::arg_mapper<format_context>().map(value);
1081+
}
10781082

10791083
template <typename T, FMT_ENABLE_IF(detail::is_float128<T>::value)>
10801084
loc_value(T) {}
@@ -3672,6 +3676,10 @@ FMT_CONSTEXPR auto write(OutputIt out, const T& value)
36723676
return formatter.format(value, ctx);
36733677
}
36743678

3679+
template <typename T>
3680+
using is_builtin =
3681+
bool_constant<std::is_same<T, int>::value || FMT_BUILTIN_TYPES>;
3682+
36753683
// An argument visitor that formats the argument and writes it via the output
36763684
// iterator. It's a class and not a generic lambda for compatibility with C++11.
36773685
template <typename Char> struct default_arg_formatter {
@@ -3681,7 +3689,15 @@ template <typename Char> struct default_arg_formatter {
36813689

36823690
void operator()(monostate) { report_error("argument not found"); }
36833691

3684-
template <typename T> void operator()(T value) { write<Char>(out, value); }
3692+
template <typename T, FMT_ENABLE_IF(is_builtin<T>::value)>
3693+
void operator()(T value) {
3694+
write<Char>(out, value);
3695+
}
3696+
3697+
template <typename T, FMT_ENABLE_IF(!is_builtin<T>::value)>
3698+
void operator()(T) {
3699+
FMT_ASSERT(false, "");
3700+
}
36853701

36863702
void operator()(typename basic_format_arg<context>::handle h) {
36873703
// Use a null locale since the default format must be unlocalized.
@@ -3699,10 +3715,17 @@ template <typename Char> struct arg_formatter {
36993715
const format_specs& specs;
37003716
locale_ref locale;
37013717

3702-
template <typename T>
3718+
template <typename T, FMT_ENABLE_IF(is_builtin<T>::value)>
37033719
FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator {
37043720
return detail::write<Char>(out, value, specs, locale);
37053721
}
3722+
3723+
template <typename T, FMT_ENABLE_IF(!is_builtin<T>::value)>
3724+
auto operator()(T) -> iterator {
3725+
FMT_ASSERT(false, "");
3726+
return out;
3727+
}
3728+
37063729
auto operator()(typename basic_format_arg<context>::handle) -> iterator {
37073730
// User-defined types are handled separately because they require access
37083731
// to the parse context.
@@ -3939,17 +3962,17 @@ FMT_FORMAT_AS(unsigned short, unsigned);
39393962
FMT_FORMAT_AS(long, detail::long_type);
39403963
FMT_FORMAT_AS(unsigned long, detail::ulong_type);
39413964
FMT_FORMAT_AS(Char*, const Char*);
3942-
FMT_FORMAT_AS(std::nullptr_t, const void*);
39433965
FMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>);
3966+
FMT_FORMAT_AS(std::nullptr_t, const void*);
39443967
FMT_FORMAT_AS(void*, const void*);
39453968

3969+
template <typename Char, size_t N>
3970+
struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {};
3971+
39463972
template <typename Char, typename Traits, typename Allocator>
39473973
class formatter<std::basic_string<Char, Traits, Allocator>, Char>
39483974
: public formatter<basic_string_view<Char>, Char> {};
39493975

3950-
template <typename Char, size_t N>
3951-
struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {};
3952-
39533976
template <typename T, typename Char>
39543977
struct formatter<T, Char,
39553978
enable_if_t<(detail::bitint_traits<T>::is_formattable)>>
@@ -4208,8 +4231,6 @@ template <typename Char> struct format_handler {
42084231
specs.precision_ref, context);
42094232
}
42104233

4211-
if (begin == end || *begin != '}')
4212-
report_error("missing '}' in format string");
42134234
arg.visit(arg_formatter<Char>{context.out(), specs, context.locale()});
42144235
return begin;
42154236
}
@@ -4246,7 +4267,7 @@ FMT_END_EXPORT
42464267

42474268
template <typename T, typename Char, type TYPE>
42484269
template <typename FormatContext>
4249-
FMT_CONSTEXPR FMT_INLINE auto native_formatter<T, Char, TYPE>::format(
4270+
FMT_CONSTEXPR auto native_formatter<T, Char, TYPE>::format(
42504271
const T& val, FormatContext& ctx) const -> decltype(ctx.out()) {
42514272
if (!specs_.dynamic())
42524273
return write<Char>(ctx.out(), val, specs_, ctx.locale());

include/fmt/printf.h

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ template <typename Char> class basic_printf_context {
3535
using char_type = Char;
3636
using parse_context_type = basic_format_parse_context<Char>;
3737
template <typename T> using formatter_type = printf_formatter<T>;
38+
enum { builtin_types = 1 };
3839

3940
/// Constructs a `printf_context` object. References to the arguments are
4041
/// stored in the context object so make sure they have appropriate lifetimes.
@@ -238,19 +239,23 @@ class printf_arg_formatter : public arg_formatter<Char> {
238239
write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
239240
}
240241

242+
template <typename T> void write(T value) {
243+
detail::write<Char>(this->out, value, this->specs, this->locale);
244+
}
245+
241246
public:
242247
printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
243248
context_type& ctx)
244249
: base(make_arg_formatter(iter, s)), context_(ctx) {}
245250

246-
void operator()(monostate value) { base::operator()(value); }
251+
void operator()(monostate value) { write(value); }
247252

248253
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
249254
void operator()(T value) {
250255
// MSVC2013 fails to compile separate overloads for bool and Char so use
251256
// std::is_same instead.
252257
if (!std::is_same<T, Char>::value) {
253-
base::operator()(value);
258+
write(value);
254259
return;
255260
}
256261
format_specs s = this->specs;
@@ -265,33 +270,33 @@ class printf_arg_formatter : public arg_formatter<Char> {
265270
// ignored for non-numeric types
266271
if (s.align() == align::none || s.align() == align::numeric)
267272
s.set_align(align::right);
268-
write<Char>(this->out, static_cast<Char>(value), s);
273+
detail::write<Char>(this->out, static_cast<Char>(value), s);
269274
}
270275

271276
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
272277
void operator()(T value) {
273-
base::operator()(value);
278+
write(value);
274279
}
275280

276281
void operator()(const char* value) {
277282
if (value)
278-
base::operator()(value);
283+
write(value);
279284
else
280285
write_null_pointer(this->specs.type() != presentation_type::pointer);
281286
}
282287

283288
void operator()(const wchar_t* value) {
284289
if (value)
285-
base::operator()(value);
290+
write(value);
286291
else
287292
write_null_pointer(this->specs.type() != presentation_type::pointer);
288293
}
289294

290-
void operator()(basic_string_view<Char> value) { base::operator()(value); }
295+
void operator()(basic_string_view<Char> value) { write(value); }
291296

292297
void operator()(const void* value) {
293298
if (value)
294-
base::operator()(value);
299+
write(value);
295300
else
296301
write_null_pointer();
297302
}

test/base-test.cc

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -372,15 +372,19 @@ VISIT_TYPE(long, long long);
372372
VISIT_TYPE(unsigned long, unsigned long long);
373373
#endif
374374

375-
#define CHECK_ARG(Char, expected, value) \
376-
{ \
377-
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
378-
EXPECT_CALL(visitor, visit(expected)); \
379-
using iterator = fmt::basic_appender<Char>; \
380-
auto var = value; \
381-
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>(var) \
382-
.visit(visitor); \
383-
}
375+
#if FMT_BUILTIN_TYPES
376+
# define CHECK_ARG(Char, expected, value) \
377+
{ \
378+
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
379+
EXPECT_CALL(visitor, visit(expected)); \
380+
using iterator = fmt::basic_appender<Char>; \
381+
auto var = value; \
382+
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>(var) \
383+
.visit(visitor); \
384+
}
385+
#else
386+
# define CHECK_ARG(Char, expected, value)
387+
#endif
384388

385389
#define CHECK_ARG_SIMPLE(value) \
386390
{ \
@@ -391,10 +395,14 @@ VISIT_TYPE(unsigned long, unsigned long long);
391395

392396
template <typename T> class numeric_arg_test : public testing::Test {};
393397

398+
#if FMT_BUILTIN_TYPES
394399
using test_types =
395400
testing::Types<bool, signed char, unsigned char, short, unsigned short, int,
396401
unsigned, long, unsigned long, long long, unsigned long long,
397402
float, double, long double>;
403+
#else
404+
using test_types = testing::Types<int>;
405+
#endif
398406
TYPED_TEST_SUITE(numeric_arg_test, test_types);
399407

400408
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
@@ -757,20 +765,12 @@ TEST(base_test, format_to_array) {
757765
EXPECT_TRUE(result.truncated);
758766
EXPECT_EQ("ABCD", fmt::string_view(buffer, 4));
759767

760-
result = fmt::format_to(buffer, "{}", std::string(1000, '*'));
768+
result = fmt::format_to(buffer, "{}", std::string(1000, '*').c_str());
761769
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
762770
EXPECT_TRUE(result.truncated);
763771
EXPECT_EQ("****", fmt::string_view(buffer, 4));
764772
}
765773

766-
#ifdef __cpp_lib_byte
767-
TEST(base_test, format_byte) {
768-
auto s = std::string();
769-
fmt::format_to(std::back_inserter(s), "{}", std::byte(42));
770-
EXPECT_EQ(s, "42");
771-
}
772-
#endif
773-
774774
// Test that check is not found by ADL.
775775
template <typename T> void check(T);
776776
TEST(base_test, adl_check) {

0 commit comments

Comments
 (0)