STL icon indicating copy to clipboard operation
STL copied to clipboard

`<format>`: Hideous compiler errors when `formatter<UDT>::format()` isn't `const`

Open StephanTLavavej opened this issue 1 year ago • 2 comments

Consider this repro from DevCom-10515914 where a user-defined formatter<UDT> forgot to mark format() as const. After #3745, we reject that as required by the Standard, but the compiler error is horrible:

C:\GitHub\STL\out\x64>type meow.cpp
#include <format>
#include <iostream>

struct MyStruct {
    uint32_t value;
    bool trueFalse;
};

enum class CustomFormat { Type1, Type2 };

namespace std {

    template <>
    struct formatter<MyStruct> {
        constexpr auto parse(format_parse_context& ctx) {
            auto it = begin(ctx);

            if (it != end(ctx)) {
                switch (*it) {
                case 'A':
                    m_format = CustomFormat::Type1;
                    break;
                case 'B':
                    m_format = CustomFormat::Type2;
                    break;
                default:
                    // don't advance the iterator for unknown format specifiers
                    // return early if we don't recognize the character
                    return it;
                }

                it++;
            }

            return it;
        }

        template <typename FormatContext>
        auto format(const MyStruct& mc, FormatContext& ctx)
#ifdef MARK_FORMAT_AS_CONST
            const
#endif
        {
            auto&& out = ctx.out();
            *out++     = '*';
            *out++     = '*';
            switch (m_format) {
            case CustomFormat::Type1:
                *out++ = 'A';
                break;
            case CustomFormat::Type2:
                *out++ = 'B';
                break;
            }
            out    = format_to(out, " - {} {}", mc.value, mc.trueFalse);
            *out++ = '*';
            return out;
        }

        CustomFormat m_format{CustomFormat::Type1};
    };
} // namespace std

int main() {
    MyStruct mystruct{959, false};

    std::cout << std::format("MyStruct: {}", mystruct) << "\n";
    std::cout << std::format("MyStruct: {:A}", mystruct) << "\n";
    std::cout << std::format("MyStruct: {:B}", mystruct) << "\n";

    std::cout << "Hello World!\n";
}
C:\GitHub\STL\out\x64>cl /EHsc /nologo /W4 /std:c++latest /MTd /Od /DMARK_FORMAT_AS_CONST meow.cpp && meow
meow.cpp
MyStruct: **A - 959 false*
MyStruct: **A - 959 false*
MyStruct: **B - 959 false*
Hello World!
C:\GitHub\STL\out\x64>cl /EHsc /nologo /W4 /std:c++latest /MTd /Od meow.cpp && meow
meow.cpp
C:\GitHub\STL\out\x64\out\inc\format(687): error C2672: 'std::_Format_arg_traits<_Context>::_Type_eraser': no matching overloaded function found
        with
        [
            _Context=std::format_context
        ]
C:\GitHub\STL\out\x64\out\inc\format(684): note: could be 'auto std::_Format_arg_traits<_Context>::_Type_eraser(void)'
        with
        [
            _Context=std::format_context
        ]
C:\GitHub\STL\out\x64\out\inc\format(687): note: the associated constraints are not satisfied
C:\GitHub\STL\out\x64\out\inc\format(847): note: the concept 'std::_Formattable_with<MyStruct,std::format_context,std::formatter<MyStruct,char>>' evaluated to false
C:\GitHub\STL\out\x64\out\inc\format(665): note: 'auto std::formatter<MyStruct,char>::format<_Context>(const MyStruct &,FormatContext &)': cannot convert 'this' pointer from 'const std::formatter<MyStruct,char>' to 'std::formatter<MyStruct,char> &'
        with
        [
            _Context=std::format_context,
            FormatContext=std::format_context
        ]
C:\GitHub\STL\out\x64\out\inc\format(665): note: Conversion loses qualifiers
C:\GitHub\STL\out\x64\out\inc\format(665): note: while trying to match the argument list '(MyStruct, _Context)'
        with
        [
            _Context=std::format_context
        ]
C:\GitHub\STL\out\x64\out\inc\format(1458): note: while evaluating constexpr function 'std::__p2286::_Format_checker<_CharT,MyStruct>::_On_replacement_field'
        with
        [
            _CharT=char
        ]
C:\GitHub\STL\out\x64\out\inc\format(1524): note: while evaluating constexpr function 'std::_Parse_replacement_field'
C:\GitHub\STL\out\x64\out\inc\format(3805): note: while evaluating constexpr function 'std::_Parse_format_string'
C:\GitHub\STL\out\x64\out\inc\format(687): note: the template instantiation context (the oldest one first) is
meow.cpp(67): note: see reference to function template instantiation 'std::basic_format_string<char,MyStruct &>::basic_format_string<char[13]>(const _Ty (&))' being compiled
        with
        [
            _Ty=char [13]
        ]
C:\GitHub\STL\out\x64\out\inc\format(3805): note: see reference to class template instantiation 'std::__p2286::_Format_checker<_CharT,MyStruct>' being compiled
        with
        [
            _CharT=char
        ]
C:\GitHub\STL\out\x64\out\inc\format(3579): note: while compiling class template member function 'std::__p2286::_Format_checker<_CharT,MyStruct>::_Format_checker(std::basic_string_view<char,std::char_traits<char>>) noexcept'
        with
        [
            _CharT=char
        ]
C:\GitHub\STL\out\x64\out\inc\format(3580): note: see reference to function template instantiation 'std::_String_view_iterator<_Traits> std::__p2286::_Compile_time_parse_format_specs<MyStruct,std::basic_format_parse_context<char>>(_ParseContext &)' being compiled
        with
        [
            _Traits=std::char_traits<char>,
            _ParseContext=std::basic_format_parse_context<char>
        ]
C:\GitHub\STL\out\x64\out\inc\format(3559): note: see reference to alias template instantiation 'std::_Format_arg_traits<_Context>::_Storage_type<MyStruct>' being compiled
        with
        [
            _Context=std::format_context
        ]
C:\GitHub\STL\out\x64\out\inc\format(3563): error C2993: 'unknown-type': is not a valid type for non-type template parameter '_Test'
C:\GitHub\STL\out\x64\out\inc\format(1458): note: while evaluating constexpr function 'std::__p2286::_Format_checker<_CharT,MyStruct>::_On_replacement_field'
        with
        [
            _CharT=char
        ]
C:\GitHub\STL\out\x64\out\inc\format(1524): note: while evaluating constexpr function 'std::_Parse_replacement_field'
C:\GitHub\STL\out\x64\out\inc\format(3805): note: while evaluating constexpr function 'std::_Parse_format_string'
C:\GitHub\STL\out\x64\out\inc\format(3565): error C2641: cannot deduce template arguments for 'std::formatter'
C:\GitHub\STL\out\x64\out\inc\format(1458): note: while evaluating constexpr function 'std::__p2286::_Format_checker<_CharT,MyStruct>::_On_replacement_field'
        with
        [
            _CharT=char
        ]
C:\GitHub\STL\out\x64\out\inc\format(1524): note: while evaluating constexpr function 'std::_Parse_replacement_field'
C:\GitHub\STL\out\x64\out\inc\format(3805): note: while evaluating constexpr function 'std::_Parse_format_string'
C:\GitHub\STL\out\x64\out\inc\format(3565): error C2783: 'std::formatter<_Ty,_CharT> std::formatter(void)': could not deduce template argument for '_Ty'
C:\GitHub\STL\out\x64\out\inc\format(3651): note: see declaration of 'std::formatter'
C:\GitHub\STL\out\x64\out\inc\format(1458): note: while evaluating constexpr function 'std::__p2286::_Format_checker<_CharT,MyStruct>::_On_replacement_field'
        with
        [
            _CharT=char
        ]
C:\GitHub\STL\out\x64\out\inc\format(1524): note: while evaluating constexpr function 'std::_Parse_replacement_field'
C:\GitHub\STL\out\x64\out\inc\format(3805): note: while evaluating constexpr function 'std::_Parse_format_string'
C:\GitHub\STL\out\x64\out\inc\format(3565): error C2780: 'std::formatter<_Ty,_CharT> std::formatter(std::formatter<_Ty,_CharT>)': expects 1 arguments - 0 provided
C:\GitHub\STL\out\x64\out\inc\format(3650): note: see declaration of 'std::formatter'
C:\GitHub\STL\out\x64\out\inc\format(1458): note: while evaluating constexpr function 'std::__p2286::_Format_checker<_CharT,MyStruct>::_On_replacement_field'
        with
        [
            _CharT=char
        ]
C:\GitHub\STL\out\x64\out\inc\format(1524): note: while evaluating constexpr function 'std::_Parse_replacement_field'
C:\GitHub\STL\out\x64\out\inc\format(3805): note: while evaluating constexpr function 'std::_Parse_format_string'
C:\GitHub\STL\out\x64\out\inc\format(3566): error C2039: 'parse': is not a member of 'std::formatter'
C:\GitHub\STL\out\x64\out\inc\format(3650): note: see declaration of 'std::formatter'
C:\GitHub\STL\out\x64\out\inc\format(1458): note: while evaluating constexpr function 'std::__p2286::_Format_checker<_CharT,MyStruct>::_On_replacement_field'
        with
        [
            _CharT=char
        ]
C:\GitHub\STL\out\x64\out\inc\format(1524): note: while evaluating constexpr function 'std::_Parse_replacement_field'
C:\GitHub\STL\out\x64\out\inc\format(3805): note: while evaluating constexpr function 'std::_Parse_format_string'
meow.cpp(67): error C3615: consteval function 'std::__p2286::_Compile_time_parse_format_specs' cannot result in a constant expression
C:\GitHub\STL\out\x64\out\inc\format(3555): note: failure was caused by control reaching the end of a consteval function
meow.cpp(67): note: the call stack of the evaluation (the oldest call first) is
meow.cpp(67): note: while evaluating function 'std::basic_format_string<char,MyStruct &>::basic_format_string<char[13]>(const _Ty (&))'
        with
        [
            _Ty=char [13]
        ]
C:\GitHub\STL\out\x64\out\inc\format(3805): note: while evaluating function 'void std::_Parse_format_string<char,std::__p2286::_Format_checker<_CharT,MyStruct>>(std::basic_string_view<char,std::char_traits<char>>,_HandlerT &&)'
        with
        [
            _CharT=char,
            _HandlerT=std::__p2286::_Format_checker<char,MyStruct>
        ]
C:\GitHub\STL\out\x64\out\inc\format(1524): note: while evaluating function 'const _CharT *std::_Parse_replacement_field<char,_HandlerT&>(const _CharT *,const _CharT *,std::__p2286::_Format_checker<_CharT,MyStruct>&)'
        with
        [
            _CharT=char,
            _HandlerT=std::__p2286::_Format_checker<char,MyStruct>
        ]
C:\GitHub\STL\out\x64\out\inc\format(1458): note: while evaluating function 'void std::__p2286::_Format_checker<_CharT,MyStruct>::_On_replacement_field(const size_t,const _CharT *) const'
        with
        [
            _CharT=char
        ]
C:\GitHub\STL\out\x64\out\inc\format(3584): note: while evaluating function 'std::_String_view_iterator<_Traits> std::__p2286::_Compile_time_parse_format_specs<MyStruct,std::basic_format_parse_context<char>>(_ParseContext &)'
        with
        [
            _Traits=std::char_traits<char>,
            _ParseContext=std::basic_format_parse_context<char>
        ]
meow.cpp(68): error C3615: consteval function 'std::__p2286::_Compile_time_parse_format_specs' cannot result in a constant expression
C:\GitHub\STL\out\x64\out\inc\format(3555): note: failure was caused by control reaching the end of a consteval function
meow.cpp(68): note: the call stack of the evaluation (the oldest call first) is
meow.cpp(68): note: while evaluating function 'std::basic_format_string<char,MyStruct &>::basic_format_string<char[15]>(const _Ty (&))'
        with
        [
            _Ty=char [15]
        ]
C:\GitHub\STL\out\x64\out\inc\format(3805): note: while evaluating function 'void std::_Parse_format_string<char,std::__p2286::_Format_checker<_CharT,MyStruct>>(std::basic_string_view<char,std::char_traits<char>>,_HandlerT &&)'
        with
        [
            _CharT=char,
            _HandlerT=std::__p2286::_Format_checker<char,MyStruct>
        ]
C:\GitHub\STL\out\x64\out\inc\format(1524): note: while evaluating function 'const _CharT *std::_Parse_replacement_field<char,_HandlerT&>(const _CharT *,const _CharT *,std::__p2286::_Format_checker<_CharT,MyStruct>&)'
        with
        [
            _CharT=char,
            _HandlerT=std::__p2286::_Format_checker<char,MyStruct>
        ]
C:\GitHub\STL\out\x64\out\inc\format(1473): note: while evaluating function 'const _CharT *std::__p2286::_Format_checker<_CharT,MyStruct>::_On_format_specs(const size_t,const _CharT *,const _CharT *)'
        with
        [
            _CharT=char
        ]
C:\GitHub\STL\out\x64\out\inc\format(3589): note: while evaluating function 'std::_String_view_iterator<_Traits> std::__p2286::_Compile_time_parse_format_specs<MyStruct,std::basic_format_parse_context<char>>(_ParseContext &)'
        with
        [
            _Traits=std::char_traits<char>,
            _ParseContext=std::basic_format_parse_context<char>
        ]
meow.cpp(69): error C3615: consteval function 'std::__p2286::_Compile_time_parse_format_specs' cannot result in a constant expression
C:\GitHub\STL\out\x64\out\inc\format(3555): note: failure was caused by control reaching the end of a consteval function
meow.cpp(69): note: the call stack of the evaluation (the oldest call first) is
meow.cpp(69): note: while evaluating function 'std::basic_format_string<char,MyStruct &>::basic_format_string<char[15]>(const _Ty (&))'
        with
        [
            _Ty=char [15]
        ]
C:\GitHub\STL\out\x64\out\inc\format(3805): note: while evaluating function 'void std::_Parse_format_string<char,std::__p2286::_Format_checker<_CharT,MyStruct>>(std::basic_string_view<char,std::char_traits<char>>,_HandlerT &&)'
        with
        [
            _CharT=char,
            _HandlerT=std::__p2286::_Format_checker<char,MyStruct>
        ]
C:\GitHub\STL\out\x64\out\inc\format(1524): note: while evaluating function 'const _CharT *std::_Parse_replacement_field<char,_HandlerT&>(const _CharT *,const _CharT *,std::__p2286::_Format_checker<_CharT,MyStruct>&)'
        with
        [
            _CharT=char,
            _HandlerT=std::__p2286::_Format_checker<char,MyStruct>
        ]
C:\GitHub\STL\out\x64\out\inc\format(1473): note: while evaluating function 'const _CharT *std::__p2286::_Format_checker<_CharT,MyStruct>::_On_format_specs(const size_t,const _CharT *,const _CharT *)'
        with
        [
            _CharT=char
        ]
C:\GitHub\STL\out\x64\out\inc\format(3589): note: while evaluating function 'std::_String_view_iterator<_Traits> std::__p2286::_Compile_time_parse_format_specs<MyStruct,std::basic_format_parse_context<char>>(_ParseContext &)'
        with
        [
            _Traits=std::char_traits<char>,
            _ParseContext=std::basic_format_parse_context<char>
        ]

This issue exists to track the possibility of improving this diagnostic without distorting the library code too much (in a way that risks damaging correctness).

StephanTLavavej avatar Nov 16 '23 01:11 StephanTLavavej

The associated constraints of _Type_eraser are being removed by #4133. I think we can investigate the errors after merging that PR.

frederick-vs-ja avatar Nov 16 '23 02:11 frederick-vs-ja

With #4133 merged, now the message is much shorter

Microsoft (R) C/C++ Optimizing Compiler Version 19.39.33218 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

test-format.cpp
stl/inc\format(3690): error C2338: static_assert failed: 'Cannot format an argument. To make type T formattable, provide a formatter<T> specialization. See N4950 [format.arg.store]/2 and [formatter.requirements].'
stl/inc\format(3690): note: the template instantiation context (the oldest one first) is
D:\test\test-format.cpp(67): note: see reference to function template instantiation 'std::string std::format<MyStruct&>(const std::basic_format_string<char,MyStruct &>,MyStruct &)' being compiled
stl/inc\format(3823): note: see reference to function template instantiation 'auto std::make_format_args<std::format_context,MyStruct&>(MyStruct &)' being compiled

cpplearner avatar Nov 30 '23 05:11 cpplearner

The remaining issue (without my mistake in and accidental fix) should only be about the const which is made required by LWG-3636.

It seems that we can reform _Formattable_with like the following. IIUC such implementation strategy is conforming due to implicit expression variations:

template <class _Ty, class _Context, class _Formatter = _Context::template formatter_type<remove_const_t<_Ty>>>
concept _Formattable_with_non_const = semiregular<_Formatter>
                                   && requires(_Formatter& __f, _Ty&& __t, _Context __fc,
                                       basic_format_parse_context<typename _Context::char_type> __pc) {
                                          { __f.parse(__pc) } -> same_as<typename decltype(__pc)::iterator>;
                                          { __f.format(__t, __fc) } -> same_as<typename _Context::iterator>;
                                      };

template <class _Ty, class _Context, class _Formatter = _Context::template formatter_type<remove_const_t<_Ty>>>
concept _Formattable_with = _Formattable_with_non_const<_Ty, _Context, _Formatter>
                         && requires(const _Formatter& __cf, _Ty&& __t, _Context __fc) {
                                { __cf.format(__t, __fc) } -> same_as<typename _Context::iterator>;
                            };

And then split static_assert's in make_format_args/make_wformat_args.

frederick-vs-ja avatar Mar 02 '24 17:03 frederick-vs-ja