https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121254
Bug ID: 121254 Summary: [modules] Specialization for std::formatter in a module causes std::format to throw error during parse Product: gcc Version: 16.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: omerfaruko at gmail dot com Target Milestone: --- This was the underlying issue I was trying to debug in Bug 121175. std::format throws during parsing if a module with a specialized std::formatter is imported. Below example demonstrates throwing import version vs working include version. ==> CMakeLists.txt <== cmake_minimum_required(VERSION 4.0) project(reprod) set(CMAKE_BUILD_TYPE Debug) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_library(reprod) target_sources(reprod PUBLIC FILE_SET CXX_MODULES FILES reprod.ccm ) add_executable(reprod_main main.cc) target_link_libraries(reprod_main PRIVATE reprod) ==> main.cc <== #include <format> #include <iostream> // throws "format error: invalid width or precision in format-spec" import reprod; // prints "1.000" and exits normally // #include "reprod.h" int main() { std::cout << std::format("{:1.3f}", 1.0) << std::endl; return 0; } ==> reprod.ccm <== module; #include <format> export module reprod; export struct Reprod {}; // Commenting this specialization out prevents the throw. template <> struct std::formatter<Reprod> { auto parse(std::format_parse_context &ctx) { return ctx.begin(); } template <typename FormatContext> auto format(Reprod value, FormatContext &ctx) const { return std::format_to(ctx.out(), "Reprod"); } }; ==> reprod.h <== #pragma once #include <format> struct Reprod {}; template <> struct std::formatter<Reprod> { auto parse(std::format_parse_context &ctx) { return ctx.begin(); } template <typename FormatContext> auto format(Reprod value, FormatContext &ctx) const { return std::format_to(ctx.out(), "Reprod"); } }; Happens with 15.1. Also tested with gcc-16-20250720 snapshot: $ export CC=/opt/gcc-16-20250720/bin/gcc $ export CXX=/opt/gcc-16-20250720/bin/g++ $ mkdir build && cd build && cmake .. && ninja --verbose && ./reprod_main -- The C compiler identification is GNU 16.0.0 -- The CXX compiler identification is GNU 16.0.0 -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: /opt/gcc-16-20250720/bin/gcc - skipped -- Detecting C compile features -- Detecting C compile features - done -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /opt/gcc-16-20250720/bin/g++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done (0.3s) -- Generating done (0.0s) -- Build files have been written to: /home/omer/src/reprod/build [1/8] /opt/gcc-16-20250720/bin/g++ -g -std=c++23 -E -x c++ /home/omer/src/reprod/reprod.ccm -MT CMakeFiles/reprod.dir/reprod.ccm.o.ddi -MD -MF CMakeFiles/reprod.dir/reprod.ccm.o.ddi.d -fmodules-ts -fdeps-file=CMakeFiles/reprod.dir/reprod.ccm.o.ddi -fdeps-target=CMakeFiles/reprod.dir/reprod.ccm.o -fdeps-format=p1689r5 -o CMakeFiles/reprod.dir/reprod.ccm.o.ddi.i [2/8] /opt/gcc-16-20250720/bin/g++ -g -std=c++23 -E -x c++ /home/omer/src/reprod/main.cc -MT CMakeFiles/reprod_main.dir/main.cc.o.ddi -MD -MF CMakeFiles/reprod_main.dir/main.cc.o.ddi.d -fmodules-ts -fdeps-file=CMakeFiles/reprod_main.dir/main.cc.o.ddi -fdeps-target=CMakeFiles/reprod_main.dir/main.cc.o -fdeps-format=p1689r5 -o CMakeFiles/reprod_main.dir/main.cc.o.ddi.i [3/8] /usr/bin/cmake -E cmake_ninja_dyndep --tdi=CMakeFiles/reprod.dir/CXXDependInfo.json --lang=CXX --modmapfmt=gcc --dd=CMakeFiles/reprod.dir/CXX.dd @CMakeFiles/reprod.dir/CXX.dd.rsp [4/8] /usr/bin/cmake -E cmake_ninja_dyndep --tdi=CMakeFiles/reprod_main.dir/CXXDependInfo.json --lang=CXX --modmapfmt=gcc --dd=CMakeFiles/reprod_main.dir/CXX.dd @CMakeFiles/reprod_main.dir/CXX.dd.rsp [5/8] /opt/gcc-16-20250720/bin/g++ -g -std=c++23 -MD -MT CMakeFiles/reprod.dir/reprod.ccm.o -MF CMakeFiles/reprod.dir/reprod.ccm.o.d -fmodules-ts -fmodule-mapper=CMakeFiles/reprod.dir/reprod.ccm.o.modmap -MD -fdeps-format=p1689r5 -x c++ -o CMakeFiles/reprod.dir/reprod.ccm.o -c /home/omer/src/reprod/reprod.ccm [6/8] : && /usr/bin/cmake -E rm -f libreprod.a && /usr/bin/ar qc libreprod.a CMakeFiles/reprod.dir/reprod.ccm.o && /usr/bin/ranlib libreprod.a && : [7/8] /opt/gcc-16-20250720/bin/g++ -g -std=c++23 -MD -MT CMakeFiles/reprod_main.dir/main.cc.o -MF CMakeFiles/reprod_main.dir/main.cc.o.d -fmodules-ts -fmodule-mapper=CMakeFiles/reprod_main.dir/main.cc.o.modmap -MD -fdeps-format=p1689r5 -x c++ -o CMakeFiles/reprod_main.dir/main.cc.o -c /home/omer/src/reprod/main.cc [8/8] : && /opt/gcc-16-20250720/bin/g++ -g -Wl,--dependency-file=CMakeFiles/reprod_main.dir/link.d CMakeFiles/reprod_main.dir/main.cc.o -o reprod_main libreprod.a && : terminate called after throwing an instance of 'std::format_error' what(): format error: invalid width or precision in format-spec [1] 132427 IOT instruction (core dumped) ./reprod_main $ gdb reprod_main GNU gdb (GDB) 16.3 Copyright (C) 2024 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-pc-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from reprod_main... (gdb) r Starting program: /home/omer/src/reprod/build/reprod_main This GDB supports auto-downloading debuginfo from the following URLs: <https://debuginfod.archlinux.org> Enable debuginfod for this session? (y or [n]) Debuginfod has been disabled. To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit. Function(s) ^std::(move|forward|as_const|(__)?addressof) will be skipped when stepping. Function(s) ^std::(shared|unique)_ptr<.*>::(get|operator) will be skipped when stepping. Function(s) ^std::(basic_string|vector|array|deque|(forward_)?list|(unordered_|flat_)?(multi)?(map|set)|span)<.*>::(c?r?(begin|end)|front|back|data|size|empty) will be skipped when stepping. Function(s) ^std::(basic_string|vector|array|deque|span)<.*>::operator.] will be skipped when stepping. [Thread debugging using libthread_db enabled] Using host libthread_db library "/usr/lib/libthread_db.so.1". terminate called after throwing an instance of 'std::format_error' what(): format error: invalid width or precision in format-spec Program received signal SIGABRT, Aborted. 0x00007ffff7a7a74c in ?? () from /usr/lib/libc.so.6 (gdb) where #0 0x00007ffff7a7a74c in ?? () from /usr/lib/libc.so.6 #1 0x00007ffff7a20dc0 in raise () from /usr/lib/libc.so.6 #2 0x00007ffff7a0857a in abort () from /usr/lib/libc.so.6 #3 0x00007ffff7c97bf8 in __gnu_cxx::__verbose_terminate_handler () at /usr/src/debug/gcc/gcc/libstdc++-v3/libsupc++/vterminate.cc:95 #4 0x00007ffff7cb1c1a in __cxxabiv1::__terminate (handler=<optimized out>) at /usr/src/debug/gcc/gcc/libstdc++-v3/libsupc++/eh_terminate.cc:48 #5 0x00007ffff7c975db in std::terminate () at /usr/src/debug/gcc/gcc/libstdc++-v3/libsupc++/eh_terminate.cc:58 #6 0x00007ffff7cb1ed6 in __cxxabiv1::__cxa_throw (obj=<optimized out>, tinfo=0x421cb8 <typeinfo for std::format_error>, dest=0x404f14 <std::format_error::~format_error()>) at /usr/src/debug/gcc/gcc/libstdc++-v3/libsupc++/eh_throw.cc:98 #7 0x0000000000404fa4 in std::__throw_format_error (__what=0x41cf38 "format error: invalid width or precision in format-spec") at /opt/gcc-16-20250720/include/c++/16.0.0/format:229 #8 0x000000000040b2af in std::__format::_Spec<char>::_S_parse_width_or_precision (__first=0x41cf74 "1.3f}", __last=0x41cf79 "", __val=@0x7fffffffd514: 0, __arg_id=@0x7fffffffd4a7: false, __pc=...) at /opt/gcc-16-20250720/include/c++/16.0.0/format:658 #9 0x000000000040b1ec in std::__format::_Spec<char>::_M_parse_width (this=0x7fffffffd514, __first=0x41cf74 "1.3f}", __last=0x41cf79 "", __pc=...) at /opt/gcc-16-20250720/include/c++/16.0.0/format:697 #10 0x000000000040c5a0 in std::__format::__formatter_fp<char>::parse (this=0x7fffffffd54c, __pc=...) at /opt/gcc-16-20250720/include/c++/16.0.0/format:2001 #11 0x000000000040ea36 in std::formatter<double, char>::parse (this=<optimized out>, __pc=...) at /opt/gcc-16-20250720/include/c++/16.0.0/format:2866 #12 std::__format::_Formatting_scanner<std::__format::_Sink_iter<char>, char>::_M_format_arg(unsigned long)::{lambda(auto:1&)#1}::operator()<double>(double&) const (__closure=0x7fffffffd6e8, __arg=@0x7fffffffd690: 1) at /opt/gcc-16-20250720/include/c++/16.0.0/format:4956 #13 0x000000000040f581 in std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::_M_visit<std::__format::_Formatting_scanner<std::__format::_Sink_iter<char>, char>::_M_format_arg(unsigned long)::{lambda(auto:1&)#1}>(std::__format::_Formatting_scanner<std::__format::_Sink_iter<char>, char>::_M_format_arg(unsigned long)::{lambda(auto:1&)#1}&&, std::__format::_Arg_t) (this=0x7fffffffd690, __vis=..., __type=std::__format::_Arg_t::_Arg_dbl) at /opt/gcc-16-20250720/include/c++/16.0.0/format:4368 #14 0x000000000040f79e in std::__format::__visit_format_arg<std::__format::_Formatting_scanner<std::__format::_Sink_iter<char>, char>::_M_format_arg(unsigned long)::{lambda(auto:1&)#1}, std::basic_format_context<std::__format::_Sink_iter<char>, char> >(std::__format::_Formatting_scanner<std::__format::_Sink_iter<char>, char>::_M_format_arg(unsigned long)::{lambda(auto:1&)#1}&&, std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >) ( __vis=..., __arg=...) at /opt/gcc-16-20250720/include/c++/16.0.0/format:4459 #15 0x000000000040f807 in std::__format::_Formatting_scanner<std::__format::_Sink_iter<char>, char>::_M_format_arg (this=0x7fffffffd840, __id=0) at /opt/gcc-16-20250720/include/c++/16.0.0/format:4946 #16 0x000000000040c337 in std::__format::_Scanner<char>::_M_on_replacement_field (this=0x7fffffffd840) at /opt/gcc-16-20250720/include/c++/16.0.0/format:4909 #17 0x000000000040bf8c in std::__format::_Scanner<char>::_M_scan (this=0x7fffffffd840) at /opt/gcc-16-20250720/include/c++/16.0.0/format:4857 #18 0x000000000040deb9 in std::__format::__do_vformat_to<std::__format::_Sink_iter<char>, char, std::basic_format_context<std::__format::_Sink_iter<char>, char> > (__out=..., __fmt="{:1.3f}", __args=std::format_args with 1 argument, __loc=0x0) at /opt/gcc-16-20250720/include/c++/16.0.0/format:5114 #19 0x00000000004052e1 in std::vformat_to<std::__format::_Sink_iter<char> >(std::__format::_Sink_iter<char>, std::basic_string_view<char, std::char_traits<char> >, std::basic_format_args<std::basic_format_context<std::__format::_Sink_iter<char>, char> >) requires output_iterator<std::__format::_Sink_iter<char>, char const&> (__out=..., __fmt="{:1.3f}", __args=std::format_args with 1 argument) at /opt/gcc-16-20250720/include/c++/16.0.0/format:5172 #20 std::vformat[abi:cxx11](std::basic_string_view<char, std::char_traits<char> >, std::basic_format_args<std::basic_format_context<std::__format::_Sink_iter<char>, char> >) (__fmt="{:1.3f}", __args=std::format_args with 1 argument) at /opt/gcc-16-20250720/include/c++/16.0.0/format:5207 #21 0x000000000040cc30 in std::format<double> (__fmt=...) at /opt/gcc-16-20250720/include/c++/16.0.0/format:5246 #22 0x00000000004044f4 in main () at /home/omer/src/reprod/main.cc:10 Include version produces correct output "1.000" and exits normally. Commenting out the std::formatter specialization in reprod.ccm also leads to the correct result.