Skip to content

[libc++] format("{0:#c}", 77) should be accepted #106136

Open
@StephanTLavavej

Description

@StephanTLavavej

https://godbolt.org/z/vac7s4rKn

#include <cassert>
#include <format>
#include <string>
using namespace std;

int main() {
    // Everyone accepts:
    assert(format("{0:#}", 77) == "77");
    assert(format("{0:#d}", 77) == "77");
    assert(format("{0:#b}", 77) == "0b1001101");
    assert(format("{0:#B}", 77) == "0B1001101");
    assert(format("{0:#o}", 77) == "0115");
    assert(format("{0:#x}", 77) == "0x4d");
    assert(format("{0:#X}", 77) == "0X4D");
    assert(format("{0:c}", 77) == "M");

    // libstdc++ 14.2 accepts, libc++ 18.1 and microsoft/STL 17.10 reject:
    assert(format("{0:#c}", 77) == "M");

    // 77 is an int, which is an arithmetic type other than charT and bool.
    // 'c' is an integer presentation type.
    // Therefore, I conclude that formatting 77 with "{0:#c}" is doubly valid.
    // Here's the Standardese:

    // N4988 [format.string.std]/7:
    // "The # option causes the alternate form to be used for the conversion.
    // This option is valid for arithmetic types other than charT and bool
    // or when an integer presentation type is specified, and not otherwise."

    // N4988 [format.string.std]/21:
    // "The available integer presentation types for integral types other than bool and charT
    // are specified in Table 72."

    // Table 72 - Meaning of type options for integer types [tab:format.type.int]
    // Type | Meaning
    // -----|--------
    // b    | to_chars(first, last, value, 2); the base prefix is 0b.
    // B    | The same as b, except that the base prefix is 0B.
    // c    | Copies the character static_cast<charT>(value) to the output.
    //      | Throws format_error if value is not in the range of representable values for charT.
    // [...]
}
<source>:18:19: error: call to consteval function 'std::basic_format_string<char, int>::basic_format_string<char[7]>' is not a constant expression
   18 |     assert(format("{0:#c}", 77) == "M");
      |                   ^
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__format/parser_std_format_spec.h:462:9: note: non-constexpr function '__throw_format_error' cannot be used in a constant expression
  462 |         std::__throw_format_error("The format specifier does not allow the alternate form option");
      |         ^
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__format/parser_std_format_spec.h:898:3: note: in call to '__parser.__validate({0, 0, 0, 0, 1, 1, 0, 0, 1}, &"an integer"[0], 4294967295)'
  898 |   __parser.__validate(__format_spec::__fields_bool, __id);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__format/parser_std_format_spec.h:905:3: note: in call to '__process_display_type_bool_string<char>(__formatter.__formatter_integer::__parser_, &"an integer"[0])'
  905 |   __format_spec::__process_display_type_bool_string(__parser, __id);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__format/parser_std_format_spec.h:964:5: note: in call to '__process_display_type_char<char>(__formatter.__formatter_integer::__parser_, &"an integer"[0])'
  964 |     __format_spec::__process_display_type_char(__parser, __id);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__format/formatter_integer.h:39:5: note: in call to '__process_parsed_integer<char>(__formatter.__formatter_integer::__parser_, &"an integer"[0])'
   39 |     __format_spec::__process_parsed_integer(__parser_, "an integer");
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__format/format_functions.h:183:26: note: in call to '__formatter.parse<std::basic_format_parse_context<char>>(basic_format_parse_context<char>{this->__str_, sizeof...(_Args)})'
  183 |   __parse_ctx.advance_to(__formatter.parse(__parse_ctx));
      |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__format/format_functions.h:206:12: note: in call to '__compile_time_validate_argument<char, int, false>(basic_format_parse_context<char>{this->__str_, sizeof...(_Args)}, _Context{__types_.data(), __handles_.data(), sizeof...(_Args)})'
  206 |     return __format::__compile_time_validate_argument<_CharT, int>(__parse_ctx, __ctx);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__format/format_functions.h:275:7: note: in call to '__compile_time_visit_format_arg<char>(basic_format_parse_context<char>{this->__str_, sizeof...(_Args)}, _Context{__types_.data(), __handles_.data(), sizeof...(_Args)}, 3)'
  275 |       __format::__compile_time_visit_format_arg(__parse_ctx, __ctx, __type);
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__format/format_functions.h:316:20: note: in call to '__handle_replacement_field<const char *, std::basic_format_parse_context<char>, std::__format::__compile_time_basic_format_context<char>>(&"{0:#c}"[1], &"{0:#c}"[6], basic_format_parse_context<char>{this->__str_, sizeof...(_Args)}, _Context{__types_.data(), __handles_.data(), sizeof...(_Args)})'
  316 |         __begin  = __format::__handle_replacement_field(__begin, __end, __parse_ctx, __ctx);
      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__format/format_functions.h:371:5: note: in call to '__vformat_to<std::basic_format_parse_context<char>, std::__format::__compile_time_basic_format_context<char>>(basic_format_parse_context<char>{this->__str_, sizeof...(_Args)}, _Context{__types_.data(), __handles_.data(), sizeof...(_Args)})'
  371 |     __format::__vformat_to(basic_format_parse_context<_CharT>{__str_, sizeof...(_Args)},
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  372 |                            _Context{__types_.data(), __handles_.data(), sizeof...(_Args)});
      |                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:18:19: note: in call to 'basic_format_string<char[7]>("{0:#c}")'
   18 |     assert(format("{0:#c}", 77) == "M");
      |                   ^~~~~~~~
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
   93 |      (static_cast <bool> (expr)                                         \
      |                           ^~~~
/opt/compiler-explorer/clang-18.1.0/bin/../include/c++/v1/__format/format_error.h:38:52: note: declared here
   38 | _LIBCPP_NORETURN inline _LIBCPP_HIDE_FROM_ABI void __throw_format_error(const char* __s) {
      |                                                    ^
1 error generated.

Metadata

Metadata

Assignees

Labels

formatC++20 std::format or std::print, and anything related to themlibc++libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions