I wanted to define a custom formatter for boost::system::error_code, but when there's any text preceding the replacement field braces compilation fails on both GCC and clang: https://godbolt.org/z/sn7xdz4MT
The formatter in question looks like this:
#include <iostream>
#include <format>
#include <boost/system/error_code.hpp>
template<>
struct std::formatter<boost::system::error_code, char> {
constexpr auto parse(auto &ctx) {
return ctx.end();
}
auto format(const boost::system::error_code &ec, auto &ctx) const
{
return std::format_to(ctx.out(), "{}", ec.message());
}
};
int main() {
std::cout << std::format("{}", boost::system::error_code()) << std::endl;
std::cout << std::format(" {}", boost::system::error_code()) << std::endl;
}
Both GCC and clang report calling non-constexpr function called __unmatched_left_brace_in_format_string
hence failing to construct the format string at compile time:
GCC:
<source>: In function 'int main()':
<source>:22:29: error: call to consteval function 'std::basic_format_string<char, boost::system::error_code>(" {}")' is not a constant expression
22 | std::cout << std::format(" {}", boost::system::error_code()) << std::endl;
| ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from <source>:2:
<source>:22:29: in 'constexpr' expansion of 'std::basic_format_string<char, boost::system::error_code>(" {}")'
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/format:4208:19: in 'constexpr' expansion of '__scanner.std::__format::_Checking_scanner<char, boost::system::error_code>::std::__format::_Scanner<char>.std::__format::_Scanner<char>::_M_scan()'
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/format:3949:30: in 'constexpr' expansion of '((std::__format::_Scanner<char>*)this)->std::__format::_Scanner<char>::_M_on_replacement_field()'
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/format:4003:60: error: call to non-'constexpr' function 'void std::__format::__unmatched_left_brace_in_format_string()'
4003 | __format::__unmatched_left_brace_in_format_string();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/format:203:3: note: 'void std::__format::__unmatched_left_brace_in_format_string()' declared here
203 | __unmatched_left_brace_in_format_string()
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Clang:
<source>:22:30: error: call to consteval function 'std::basic_format_string<char, boost::system::error_code>::basic_format_string<char[4]>' is not a constant expression
22 | std::cout << std::format(" {}", boost::system::error_code()) << std::endl;
| ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/15.0.0/../../../../include/c++/15.0.0/format:4120:4: note: non-constexpr function '__unmatched_left_brace_in_format_string' cannot be used in a constant expression
4120 | __format::__unmatched_left_brace_in_format_string();
| ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/15.0.0/../../../../include/c++/15.0.0/format:4066:7: note: in call to 'this->_M_on_replacement_field()'
4066 | _M_on_replacement_field();
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/15.0.0/../../../../include/c++/15.0.0/format:4377:2: note: in call to '__scanner._M_scan()'
4377 | __scanner._M_scan();
| ^~~~~~~~~~~~~~~~~~~
<source>:22:30: note: in call to 'basic_format_string<char[4]>(" {}")'
22 | std::cout << std::format(" {}", boost::system::error_code()) << std::endl;
| ^~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/15.0.0/../../../../include/c++/15.0.0/format:209:3: note: declared here
209 | __unmatched_left_brace_in_format_string()
| ^
Is this a problem with my code?
The problem is that your parse function isn't behaving as GCC/Clang want - It should point to the closing '}' in the parse context. In your case, it's pointing to one after, which is consuming the '}', so the compiler is failing
GCC/Clang expect you to consume values from the parse context, and return the first unmatched character. I can't really explain why it allows the "{}" format, nor can I really confirm if their behavior is correct by the specification.
Replace the body of your parse function with
return ctx.begin();
Given your format function, you can also choose to inherit your formatter from the string formatter, as recommended by 康桓瑋 in the comments.