c++c++20fmtstdformat

Why are GCC and clang telling me that there's an unmatched left brace in my format string when using a custom type formatter?


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?


Solution

  • 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.