With FMT_USE_LOCALE=0, L does three different things depending on the argument type, and one of them still reads the global locale. I think two of the three are regressions, but the right contract is your call, so this is an issue rather than a PR.
Repro
Head (8dc5d3f), MSVC 19.51, /std:c++20, header-only, global locale set to de-DE:
#define FMT_USE_LOCALE 0
#include <fmt/chrono.h>
std::locale::global(std::locale("de-DE"));
fmt::format("{:L}", 1234567) // "1234567" L ignored
fmt::format("{:L}", 1234.5) // "1,234.5" stub numpunct applied
fmt::format("{:L%a}", tm) // "Mon" inert
fmt::format("{:L}", std::chrono::Monday) // "Mo" global locale
| Path |
Behaviour |
Why |
| int |
inert |
the locale-off write_loc returns false (format.h:2225), so it falls to write_int (format.h:2371) |
| float |
stub grouping "\03" / ',' |
do_write_float always runs Grouping(loc, specs.localized()) (format.h:2743, 2759, 2793) |
std::tm |
inert |
get_locale(static_cast<bool>(loc_ref), ...), and operator bool is false (chrono.h:2148, core.h:919) |
| weekday / month / duration |
global locale |
get_locale(localized(), ...) (chrono.h:1705, 1925, 1977). With the loc.get<std::locale>() arm compiled out, get_locale default-constructs std::locale(), which is the global locale, not the classic one. That then reaches std::use_facet<std::time_put> at chrono.h:389. |
On the numeric split
b98ffb7 ("Improve locale handling", 2022-09-04) moved the integer localized path onto write_loc, whose locale-off form returns false, so integers silently stopped grouping. Floats never used write_loc for grouping and kept theirs.
Two years later b90b4bc ("Remove FMT_STATIC_THOUSANDS_SEPARATOR in favor of FMT_USE_LOCALE") hard-coded ',' into the stub numpunct rather than dropping it, which reads like the static-separator behaviour was deliberately retained. If that is right, the integer path is the regression, not the float path. I did not want to assume it either way.
On the chrono path
Whatever L should mean here, consulting the global locale in a build with locale support disabled looks unintended on its own. formatter<std::tm> escapes it only because locale_ref::operator bool is false (#4627).
(#4940 fixes an adjacent case where formatter<year_month_day> constructs a locale unconditionally, but that one is independent of L.)
Why nothing caught this
nolocale-test has been compiling with locale enabled since b90b4bc, because FMT_STATIC_THOUSANDS_SEPARATOR no longer exists under include/. #4939 fixes that.
Question
Which contract do you want for L when locale support is off - reject it, ignore it, or honour it via the stub? Happy to write the patch once you pick.
One implementation note: rejecting has to happen in basic_specs::set_localized, not in parse_format_specs. Only one of the six call sites is the core parser (core.h:1540); the other five are chrono (chrono.h:1914, 1966, 2063, 2110, 2127) and bypass it. Ignoring can be a no-op setter, and honouring is a change to the integer write path - those two do not have the same constraint.
With
FMT_USE_LOCALE=0,Ldoes three different things depending on the argument type, and one of them still reads the global locale. I think two of the three are regressions, but the right contract is your call, so this is an issue rather than a PR.Repro
Head (8dc5d3f), MSVC 19.51,
/std:c++20, header-only, global locale set tode-DE:write_locreturns false (format.h:2225), so it falls towrite_int(format.h:2371)"\03"/','do_write_floatalways runsGrouping(loc, specs.localized())(format.h:2743,2759,2793)std::tmget_locale(static_cast<bool>(loc_ref), ...), andoperator boolis false (chrono.h:2148,core.h:919)get_locale(localized(), ...)(chrono.h:1705,1925,1977). With theloc.get<std::locale>()arm compiled out,get_localedefault-constructsstd::locale(), which is the global locale, not the classic one. That then reachesstd::use_facet<std::time_put>atchrono.h:389.On the numeric split
b98ffb7 ("Improve locale handling", 2022-09-04) moved the integer localized path onto
write_loc, whose locale-off form returns false, so integers silently stopped grouping. Floats never usedwrite_locfor grouping and kept theirs.Two years later b90b4bc ("Remove FMT_STATIC_THOUSANDS_SEPARATOR in favor of FMT_USE_LOCALE") hard-coded
','into the stubnumpunctrather than dropping it, which reads like the static-separator behaviour was deliberately retained. If that is right, the integer path is the regression, not the float path. I did not want to assume it either way.On the chrono path
Whatever
Lshould mean here, consulting the global locale in a build with locale support disabled looks unintended on its own.formatter<std::tm>escapes it only becauselocale_ref::operator boolis false (#4627).(#4940 fixes an adjacent case where
formatter<year_month_day>constructs a locale unconditionally, but that one is independent ofL.)Why nothing caught this
nolocale-testhas been compiling with locale enabled since b90b4bc, becauseFMT_STATIC_THOUSANDS_SEPARATORno longer exists underinclude/. #4939 fixes that.Question
Which contract do you want for
Lwhen locale support is off - reject it, ignore it, or honour it via the stub? Happy to write the patch once you pick.One implementation note: rejecting has to happen in
basic_specs::set_localized, not inparse_format_specs. Only one of the six call sites is the core parser (core.h:1540); the other five are chrono (chrono.h:1914,1966,2063,2110,2127) and bypass it. Ignoring can be a no-op setter, and honouring is a change to the integer write path - those two do not have the same constraint.