https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121254
--- Comment #4 from Omer Ozarslan <omerfaruko at gmail dot com> --- I debugged this a bit yesterday. __parse_integer returns nullptr for the second argument here: https://github.com/gcc-mirror/gcc/blame/62f8a246bbaa1a1f5aedba4c84f7fe4c7eca799f/libstdc%2B%2B-v3/include/std/format#L422-L450 if (__detail::__from_chars_alnum<true>(__first, __last, __val, 10) && __first != __start) [[likely]] return {__val, __first}; [...] return {0, nullptr}; and the caller in turn throws here: https://github.com/gcc-mirror/gcc/blob/62f8a246bbaa1a1f5aedba4c84f7fe4c7eca799f/libstdc%2B%2B-v3/include/std/format#L656-L659 auto [__v, __ptr] = __format::__parse_integer(__first, __last); if (!__ptr) __throw_format_error("format error: invalid width or precision " "in format-spec"); The interesting bit is that __from_chars_alnum returns with __val = 1. But despite that, __first doesn't advance past __start and __first != __start returns false. So why doesn't it advance although __first is taken by reference in __from_chars_alnum? I confirmed __parse_integer emits different ASM for module version vs include version. For some reason, it seems like the __first reference passed to __from_chars_alnum is not the same as the __first reference compared to __start in the same check, i.e. some other address in the stack initialized with the value of __first is what's passed by reference to __from_chars_alnum. (Attached extracted ASMs for bad and good versions.) Then I figured GCC can also dump trees. The tree with module version (bad one) has this interesting thing going on: struct pair std::__format::__parse_integer<char> (const char * __first, const char * __last) { bool retval.119; bool iftmp.120; const char * __first.121; // Notice this struct pair D.173282; struct pair D.118538; const char * __first.122; struct pair D.118539; int D.118540; <<< Unknown tree: nullptr_type >>> D.118541; [...] __first.121 = __first; // __first.121 is passed by reference below. _1 = std::__detail::__from_chars_alnum<true, short unsigned int> (&__first.121, __last, &__val, 10); if (_1 != 0) goto <D.173278>; else goto <D.173275>; <D.173278>: // but __first is compared below if (__first != __start) goto <D.173279>; else goto <D.173275>; This is different from include version: struct pair std::__format::__parse_integer<char> (const char * __first, const char * __last) { bool retval.116; bool iftmp.117; struct pair D.135974; struct pair D.94694; struct pair D.94727; int D.94720; <<< Unknown tree: nullptr_type >>> D.94721; [...] _2 = std::__detail::__from_chars_alnum<true, short unsigned int> (&__first, __last, &__val, 10); if (_2 != 0) goto <D.135970>; else goto <D.135968>; <D.135970>: __first.118_3 = __first; if (__start != __first.118_3) goto <D.135971>; else goto <D.135968>; I don't know why would that happen. Full trees were too large to attach even compressed.