@@ -908,6 +908,24 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
908908 }
909909 };
910910
911+ // Type-erased handler passed to parse_quantity_specs at runtime.
912+ // Being a concrete (non-template) type it ensures parse_quantity_specs is instantiated
913+ // only once for the runtime path, regardless of how many OutputIt types are used.
914+ struct erased_handler {
915+ void * self;
916+ void (*on_number_fn)(void *);
917+ void (*on_maybe_space_fn)(void *);
918+ void (*on_unit_fn)(void *);
919+ void (*on_dimension_fn)(void *);
920+ void (*on_text_fn)(void *, const Char*, const Char*);
921+
922+ void on_number () { on_number_fn (self); }
923+ void on_maybe_space () { on_maybe_space_fn (self); }
924+ void on_unit () { on_unit_fn (self); }
925+ void on_dimension () { on_dimension_fn (self); }
926+ void on_text (const Char* begin, const Char* end) { on_text_fn (self, begin, end); }
927+ };
928+
911929 template <typename OutputIt>
912930 struct quantity_formatter {
913931 const formatter& f;
@@ -933,10 +951,21 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
933951 out = MP_UNITS_STD_FMT::vformat_to (out, locale, f.dimension_format_str_ ,
934952 MP_UNITS_STD_FMT::make_format_args (q.dimension ));
935953 }
936- template <std::forward_iterator It>
937- void on_text (It begin, It end) const
954+ void on_text (const Char* begin, const Char* end) { mp_units::detail::copy (begin, end, out); }
955+
956+ // Static trampolines for erased_handler — tiny, do not drag in parse_quantity_specs.
957+ static void on_number_erased (void * p) { static_cast <quantity_formatter*>(p)->on_number (); }
958+ static void on_maybe_space_erased (void * p) { static_cast <quantity_formatter*>(p)->on_maybe_space (); }
959+ static void on_unit_erased (void * p) { static_cast <quantity_formatter*>(p)->on_unit (); }
960+ static void on_dimension_erased (void * p) { static_cast <quantity_formatter*>(p)->on_dimension (); }
961+ static void on_text_erased (void * p, const Char* b, const Char* e)
962+ {
963+ static_cast <quantity_formatter*>(p)->on_text (b, e);
964+ }
965+
966+ erased_handler erase ()
938967 {
939- mp_units::detail::copy (begin, end, out) ;
968+ return { this , on_number_erased, on_maybe_space_erased, on_unit_erased, on_dimension_erased, on_text_erased} ;
940969 }
941970 };
942971 template <typename OutputIt, typename ... Args>
@@ -1042,20 +1071,23 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
10421071 return begin;
10431072 }
10441073
1045- template <typename OutputIt, typename FormatContext>
1046- OutputIt format_quantity (OutputIt out, const quantity_t & q, FormatContext& ctx) const
1074+ // Writes the quantity content to `out`. Used for the width-buffering path.
1075+ template <typename OutputIt>
1076+ OutputIt format_to (OutputIt out, const quantity_t & q, const std::locale& locale) const
10471077 {
1048- const std::locale locale = MP_UNITS_FMT_LOCALE (ctx.locale ());
10491078 if (modifiers_format_str_.empty ()) {
1050- // default
1079+ // default layout: rep [space] unit
10511080 out = MP_UNITS_STD_FMT::vformat_to (out, locale, rep_format_str_,
10521081 MP_UNITS_STD_FMT::make_format_args (q.numerical_value_ref_in (q.unit )));
10531082 if constexpr (mp_units::space_before_unit_symbol<unit>) *out++ = ' ' ;
10541083 return MP_UNITS_STD_FMT::vformat_to (out, locale, unit_format_str_, MP_UNITS_STD_FMT::make_format_args (q.unit ));
10551084 }
10561085 // user provided format
10571086 quantity_formatter f{*this , out, q, locale};
1058- parse_quantity_specs (modifiers_format_str_.data (), modifiers_format_str_.data () + modifiers_format_str_.size (), f);
1087+ // Use erased_handler so parse_quantity_specs is instantiated only once (with erased_handler),
1088+ // not once per OutputIt type.
1089+ auto eh = f.erase ();
1090+ parse_quantity_specs (modifiers_format_str_.data (), modifiers_format_str_.data () + modifiers_format_str_.size (), eh);
10591091 return f.out ;
10601092 }
10611093
@@ -1081,7 +1113,9 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
10811113 mp_units::detail::handle_dynamic_spec<mp_units::detail::width_checker>(specs.width , specs.width_ref , ctx);
10821114
10831115 if (specs.width == 0 && modifiers_format_str_.empty ()) {
1084- // Common fast path: call pre-parsed sub-formatters directly — no vformat_to, no allocation
1116+ // Fast path: no modifiers and no width — call pre-parsed sub-formatters directly.
1117+ // No vformat_to, no locale extraction, no allocation. Works correctly even when
1118+ // N[...]/U[...]/D[...] sub-specs were given: the spec is already encoded in the formatters.
10851119 ctx.advance_to (rep_formatter_.format (q.numerical_value_ref_in (q.unit ), ctx));
10861120 if constexpr (mp_units::space_before_unit_symbol<unit>) {
10871121 auto it = ctx.out ();
@@ -1090,13 +1124,13 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
10901124 }
10911125 return unit_formatter_.format (q.unit , ctx);
10921126 }
1093- if (specs. width == 0 ) {
1094- // Custom modifiers, no width
1095- format_quantity (ctx.out (), q, ctx );
1096- return ctx.out ();
1097- }
1127+
1128+ // Slow path: modifiers or width — must buffer or use vformat_to.
1129+ const std::locale locale = MP_UNITS_FMT_LOCALE (ctx.locale () );
1130+ if (specs. width == 0 ) return format_to ( ctx.out (), q, locale );
1131+
10981132 std::basic_string<Char> quantity_buffer;
1099- format_quantity (std::back_inserter (quantity_buffer), q, ctx );
1133+ format_to (std::back_inserter (quantity_buffer), q, locale );
11001134 return mp_units::detail::write_padded<Char>(ctx.out (), std::basic_string_view<Char>{quantity_buffer}, specs.width ,
11011135 specs.align , specs.fill );
11021136 }
0 commit comments