diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 3e357089464..0d7f18f465f 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -397,39 +397,6 @@ namespace ranges { }; inline constexpr _For_each_n_fn for_each_n{_Not_quite_object::_Construct_tag{}}; - - // VARIABLE ranges::find - class _Find_fn : private _Not_quite_object { - public: - using _Not_quite_object::_Not_quite_object; - - // clang-format off - template _Se, class _Ty, class _Pj = identity> - requires indirect_binary_predicate, const _Ty*> - _NODISCARD constexpr _It operator()(_It _First, _Se _Last, const _Ty& _Val, _Pj _Proj = {}) const { - _Adl_verify_range(_First, _Last); - auto _UResult = _RANGES _Find_unchecked( - _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Val, _Pass_fn(_Proj)); - - _Seek_wrapped(_First, _STD move(_UResult)); - return _First; - } - - template - requires indirect_binary_predicate, _Pj>, const _Ty*> - _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()( - _Rng&& _Range, const _Ty& _Val, _Pj _Proj = {}) const { - auto _First = _RANGES begin(_Range); - auto _UResult = - _RANGES _Find_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), _Val, _Pass_fn(_Proj)); - - _Seek_wrapped(_First, _STD move(_UResult)); - return _First; - } - // clang-format on - }; - - inline constexpr _Find_fn find{_Not_quite_object::_Construct_tag{}}; } // namespace ranges #endif // __cpp_lib_concepts @@ -1380,36 +1347,6 @@ namespace ranges { inline constexpr _None_of_fn none_of{_Not_quite_object::_Construct_tag{}}; - // VARIABLE ranges::copy - class _Copy_fn : private _Not_quite_object { - public: - using _Not_quite_object::_Not_quite_object; - - // clang-format off - template _Se, weakly_incrementable _Out> - requires indirectly_copyable<_It, _Out> - constexpr copy_result<_It, _Out> operator()(_It _First, _Se _Last, _Out _Result) const { - _Adl_verify_range(_First, _Last); - auto _UResult = _RANGES _Copy_unchecked( - _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _STD move(_Result)); - _Seek_wrapped(_First, _STD move(_UResult.in)); - return {_STD move(_First), _STD move(_UResult.out)}; - } - - template - requires indirectly_copyable, _Out> - constexpr copy_result, _Out> operator()(_Rng&& _Range, _Out _Result) const { - auto _First = _RANGES begin(_Range); - auto _UResult = - _RANGES _Copy_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), _STD move(_Result)); - _Seek_wrapped(_First, _STD move(_UResult.in)); - return {_STD move(_First), _STD move(_UResult.out)}; - } - // clang-format on - }; - - inline constexpr _Copy_fn copy{_Not_quite_object::_Construct_tag{}}; - // ALIAS TEMPLATE copy_n_result template using copy_n_result = in_out_result<_In, _Out>; diff --git a/stl/inc/chrono b/stl/inc/chrono index 2cf1fc3694e..19be9514c7b 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5195,8 +5195,8 @@ namespace chrono { template struct _Chrono_specs { - _CharT _Lit_char = _CharT{0}; // any char other than {, }, % - char _Modifier = '\0'; // either E or O + _CharT _Lit_char = _CharT{0}; // any character other than '{', '}', or '%' + char _Modifier = '\0'; // either 'E' or 'O' char _Type = '\0'; }; @@ -5208,7 +5208,7 @@ namespace chrono { int _Dynamic_precision_index = -1; _Align _Alignment = _Align::_None; // At most one codepoint (so one char32_t or four utf-8 char8_t) - _CharT _Fill[4] = {' ', _CharT{0}, _CharT{0}, _CharT{0}}; + _CharT _Fill[4 / sizeof(_CharT)] = {_CharT{' '}}; // recursive definition in grammar, so could have any number of these with literal chars vector<_Chrono_specs<_CharT>> _Chrono_specs_list; }; @@ -5226,12 +5226,12 @@ namespace chrono { // same as _Specs_setter constexpr void _On_fill(basic_string_view<_CharT> _Sv) { - if (_Sv.size() > 4) { + if (_Sv.size() > _STD size(_Specs._Fill)) { _THROW(format_error("Invalid fill (too long).")); } - _STD fill(_Specs._Fill, _Specs._Fill + 4, _CharT{}); - _STD copy(_Sv.begin(), _Sv.end(), _Specs._Fill); + const auto _Pos = _STD _Copy_unchecked(_Sv._Unchecked_begin(), _Sv._Unchecked_end(), _Specs._Fill); + _STD fill(_Pos, _STD end(_Specs._Fill), _CharT{}); } constexpr void _On_width(int _Width) { @@ -5270,7 +5270,8 @@ namespace chrono { template _Callbacks_type> _NODISCARD constexpr const _CharT* _Parse_conversion_specs( const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { - ++_Begin; // move past % + _STL_INTERNAL_CHECK(*_Begin == '%'); + ++_Begin; if (_Begin == _End || *_Begin == '}') { _THROW(format_error("Invalid format string.")); } diff --git a/stl/inc/format b/stl/inc/format index b785cdeee41..00857c938fa 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -145,6 +145,7 @@ public: constexpr explicit basic_format_parse_context(basic_string_view<_CharT> _Fmt, size_t _Num_args_ = 0) noexcept : _Format_string(_Fmt), _Num_args(_Num_args_) {} + basic_format_parse_context(const basic_format_parse_context&) = delete; basic_format_parse_context& operator=(const basic_format_parse_context&) = delete; @@ -385,15 +386,92 @@ _NODISCARD constexpr const _CharT* _Parse_arg_id( throw format_error("Invalid format string."); } +_NODISCARD inline int _Code_units_in_next_character(const char* _First, const char* _Last, const _Cvtvec& _Cvt) { + // Returns a count of the number of code units that compose the first encoded character in + // [_First, _Last), or -1 if [_First, _Last) doesn't contain an entire encoded character or + // *_First is not a valid lead byte. + _STL_INTERNAL_CHECK(_First < _Last); + + switch (_Cvt._Mbcurmax) { + default: + _STL_INTERNAL_CHECK(!"Bad number of encoding units for this code page"); + [[fallthrough]]; + case 1: + return 1; // all characters have only one code unit + + case 2: + { + wchar_t _Wide; + mbstate_t _St{}; + const auto _Len = static_cast(_Last - _First); + const int _Result = _Mbrtowc(&_Wide, _First, _Len, &_St, &_Cvt); + if (_Result > 0) { + return _Result; + } else if (_Result < 0) { // invalid or incomplete encoded character + return -1; + } else { // next code unit is '\0' + return 1; + } + } + + case 4: // Assume UTF-8 (as does _Mbrtowc) + { + const auto _Ch = static_cast(*_First); + if (_Ch < 0b1000'0000u) { + return 1; + } + + const auto _Len = static_cast(_Last - _First); + + if (_Ch < 0b1110'0000u) { + // check for non-lead byte or partial 2-byte encoded character + return (_Ch >= 0b1100'0000u && _Len >= 2) ? 2 : -1; + } + + if (_Ch < 0b1111'0000u) { + // check for partial 3-byte encoded character + return (_Len >= 3) ? 3 : -1; + } + + // check for partial 4-byte encoded character + return (_Len >= 4) ? 4 : -1; + } + } +} + +_NODISCARD inline int _Code_units_in_next_character(const wchar_t* _First, const wchar_t* _Last, const _Cvtvec&) { + // Returns a count of the number of code units that compose the first encoded character in + // [_First, _Last), or -1 if [_First, _Last) doesn't contain an entire encoded character or + // *_First is an unpaired surrogate. + + _STL_INTERNAL_CHECK(_First < _Last); + + if (*_First < 0xD800u || *_First >= 0xE000u) { + return 1; + } + + if (*_First >= 0xDC00u) { // unpaired low surrogate + return -1; + } + + if (++_First == _Last || *_First < 0xDC00u || *_First >= 0xE000u) { // unpaired high surrogate + return -1; + } + + return 2; // surrogate pair +} + template _Callbacks_type> -_NODISCARD constexpr const _CharT* _Parse_align( - const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { +_NODISCARD const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { _STL_INTERNAL_CHECK(_Begin != _End && *_Begin != '}'); // align and fill auto _Parsed_align = _Align::_None; - // TODO: should increment one code point - auto _Align_pt = _Begin + 1; + const int _Units = _Code_units_in_next_character(_Begin, _End, _Getcvt()); + if (_Units < 0) { // invalid fill character encoding + throw format_error("Invalid format string."); + } + auto _Align_pt = _Begin + _Units; if (_Align_pt == _End) { _Align_pt = _Begin; } @@ -469,6 +547,7 @@ template struct _Id_adapter { basic_format_parse_context<_CharT>& _Parse_context; size_t _Arg_id = static_cast(-1); + constexpr void _On_auto_id() { _Arg_id = _Parse_context.next_arg_id(); _STL_INTERNAL_CHECK(_Arg_id != static_cast(-1)); @@ -637,17 +716,39 @@ _NODISCARD constexpr const _CharT* _Parse_replacement_field( return _Begin + 1; } +template +const _CharT* _Find_encoded(const _CharT* _First, const _CharT* _Last, const _CharT _Val, const _Cvtvec& _Cvt) { + // Returns the first occurrence of _Val as an encoded character (and not, for example, as a + // continuation byte) in [_First, _Last). + if (_Cvt._Mbcurmax == 1 || _Cvt._Mbcurmax == 4) { + // As above and in _Mbrtowc, assume 4-byte encodings are UTF-8 + return _Find_unchecked(_First, _Last, _Val); + } + + while (_First != _Last && *_First != _Val) { + const int _Units = _Code_units_in_next_character(_First, _Last, _Cvt); + if (_Units < 0) { + throw format_error("Invalid encoded character in format string."); + } + _First += _Units; + } + + return _First; +} + template _HandlerT> -constexpr void _Parse_format_string(basic_string_view<_CharT> _Format_str, _HandlerT&& _Handler) { - auto _Begin = _Format_str.data(); - auto _End = _Begin + _Format_str.size(); +void _Parse_format_string(basic_string_view<_CharT> _Format_str, _HandlerT&& _Handler) { + auto _Begin = _Format_str.data(); + auto _End = _Begin + _Format_str.size(); + const _Cvtvec& _Cvt = _Getcvt(); + while (_Begin != _End) { const _CharT* _OpeningCurl = _Begin; if (*_Begin != '{') { - // we didn't start at an opening curl, find the next one - _OpeningCurl = _Find_unchecked(_Begin + 1, _End, '{'); + _OpeningCurl = _Find_encoded(_Begin, _End, _CharT{'{'}, _Cvt); + for (;;) { - const _CharT* _ClosingCurl = _Find_unchecked(_Begin, _OpeningCurl, '}'); + const _CharT* _ClosingCurl = _Find_encoded(_Begin, _OpeningCurl, _CharT{'}'}, _Cvt); // In this case there are neither closing nor opening curls in [_Begin, _OpenCurl) // Write the whole thing out. @@ -678,7 +779,6 @@ constexpr void _Parse_format_string(basic_string_view<_CharT> _Format_str, _Hand } } - template struct _Basic_format_specs { int _Width = 0; @@ -690,7 +790,7 @@ struct _Basic_format_specs { bool _Localized = false; bool _Leading_zero = false; // At most one codepoint (so one char32_t or four utf-8 char8_t). - _CharT _Fill[4] = {' ', _CharT{0}, _CharT{0}, _CharT{0}}; + _CharT _Fill[4 / sizeof(_CharT)] = {_CharT{' '}}; }; // Adds width and precision references to _Basic_format_specs. @@ -702,7 +802,7 @@ struct _Dynamic_format_specs : _Basic_format_specs<_CharT> { int _Dynamic_precision_index = -1; }; -// Model of _Parse_specs_callbacks that fills a _Basic_format_specs with the parsed data. +// Model of _Parse_spec_callbacks that fills a _Basic_format_specs with the parsed data. template class _Specs_setter { public: @@ -713,11 +813,12 @@ public: } constexpr void _On_fill(basic_string_view<_CharT> _Sv) { - if (_Sv.size() > 4) { + if (_Sv.size() > _STD size(_Specs._Fill)) { throw format_error("Invalid fill (too long)."); } - _STD fill(_Specs._Fill, _Specs._Fill + 4, _CharT{}); - _STD copy(_Sv.begin(), _Sv.end(), _Specs._Fill); + + const auto _Pos = _STD _Copy_unchecked(_Sv._Unchecked_begin(), _Sv._Unchecked_end(), _Specs._Fill); + _STD fill(_Pos, _STD end(_Specs._Fill), _CharT{}); } constexpr void _On_sign(_Sign _Sgn) { @@ -1396,7 +1497,7 @@ _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _CharT* _Value) { template _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const basic_string_view<_CharT> _Value) { - return _RANGES _Copy_unchecked(_Value.begin(), _Value.end(), _STD move(_Out)).out; + return _RANGES copy(_Value, _STD move(_Out)).out; } template @@ -1428,10 +1529,18 @@ _NODISCARD _OutputIt _Write_aligned(_OutputIt _Out, const int _Width, const _Bas } } - // TRANSITION, add support for unicode/wide formats - _Out = _RANGES fill_n(_STD move(_Out), _Fill_left, _Specs._Fill[0]); + const basic_string_view<_CharT> _Fill_char{_Specs._Fill, _RANGES find(_Specs._Fill, '\0')}; + for (; _Fill_left > 0; --_Fill_left) { + _Out = _RANGES copy(_Fill_char, _STD move(_Out)).out; + } + _Out = _Fn(_STD move(_Out)); - return _RANGES fill_n(_STD move(_Out), _Fill_right, _Specs._Fill[0]); + + for (; _Fill_right > 0; --_Fill_right) { + _Out = _RANGES copy(_Fill_char, _STD move(_Out)).out; + } + + return _Out; } template @@ -1985,6 +2094,119 @@ _NODISCARD _OutputIt _Fmt_write( return _Fmt_write(_STD move(_Out), basic_string_view<_CharT>{_Value}, _Specs, _Locale); } +inline constexpr char16_t _Width_estimate_low_intervals[] = { // Per N4885 [format.string.std]/11 + 0x1100u, 0x1160u, 0x2329u, 0x232Bu, 0x2E80u, 0x303Fu, 0x3040u, 0xA4D0u, 0xAC00u, 0xD7A4u, 0xF900u, 0xFB00u, 0xFE10u, + 0xFE1Au, 0xFE30u, 0xFE70u, 0xFF00u, 0xFF61u, 0xFFE0u, 0xFFE7u}; + +inline constexpr char32_t _Width_estimate_high_intervals[] = { // Per N4885 [format.string.std]/11 + 0x1F300u, 0x1F650u, 0x1F900u, 0x1FA00u, 0x20000u, 0x2FFFEu, 0x30000u, 0x3FFFEu}; + +template +_NODISCARD constexpr int _Unicode_width_estimate(const char32_t _Ch) noexcept { + // Computes the width estimation for Unicode characters from N4885 [format.string.std]/11 + int _Result = 1; + for (const auto& _Bound : _Bounds) { + if (_Ch < _Bound) { + return _Result; + } + _Result ^= 1; + } + return 1; +} + +_NODISCARD inline int _Estimate_character_width(const char* _Ptr, const int _Units, const _Cvtvec& _Cvt) { + // Return an estimate for the width of the character composed of _Units code units, + // whose first code unit is denoted by _Ptr. + if (_Cvt._Mbcurmax != 4) { + // not a Unicode encoding; estimate width == number of code units + return _Units; + } + + // assume UTF-8 + auto _Ch = static_cast(*_Ptr); + switch (_Units) { + default: + case 1: + case 2: + return 1; + case 3: + _Ch &= 0b1111u; + break; + case 4: + _Ch &= 0b111u; + break; + } + + for (int _Idx = 1; _Idx < _Units; ++_Idx) { + _Ch = _Ch << 6 | (_Ptr[_Idx] & 0b11'1111u); + } + + if (_Units == 3) { + return _Unicode_width_estimate<_Width_estimate_low_intervals>(_Ch); + } + + return _Unicode_width_estimate<_Width_estimate_high_intervals>(_Ch); +} + +_NODISCARD inline int _Estimate_character_width(const wchar_t* _Ptr, const int _Units, const _Cvtvec&) { + // Return an estimate for the width of the character composed of _Units code units, + // whose first code unit is denoted by _Ptr. + auto _Ch = static_cast(*_Ptr); + if (_Units == 1) { + return _Unicode_width_estimate<_Width_estimate_low_intervals>(_Ch); + } + + // surrogate pair + _Ch = (_Ch - 0xD8000u) << 10; + _Ch += static_cast(_Ptr[1]) - 0xDC00u; + _Ch += 0x10000u; + return _Unicode_width_estimate<_Width_estimate_high_intervals>(_Ch); +} + +template +_NODISCARD const _CharT* _Measure_string_prefix(const basic_string_view<_CharT> _Value, int& _Width) { + // Returns a pointer past-the-end of the largest prefix of _Value that fits in _Width, or all + // of _Value if _Width is negative. Updates _Width to the estimated width of that prefix. + const int _Max_width = _Width; + auto _Pos = _Value.data(); + const auto _Last = _Pos + _Value.size(); + int _Estimated_width = 0; // the estimated width of [_Value.data(), _Pos) + const _Cvtvec& _Cvt = _Getcvt(); + constexpr auto _Max_int = (numeric_limits::max)(); + + while (_Pos != _Last) { + if (_Estimated_width == _Max_width && _Max_width >= 0) { + // We're at our maximum length + break; + } + + // TRANSITION, extended grapheme clustering + const int _Units = _Code_units_in_next_character(_Pos, _Last, _Cvt); + const int _Character_width = _Estimate_character_width(_Pos, _Units, _Cvt); + + if (_Max_int - _Character_width < _Estimated_width) { // avoid overflow + // Either _Max_width isn't set, or adding this character will exceed it. + if (_Max_width < 0) { // unset; saturate width estimate and take all characters + _Estimated_width = _Max_int; + _Pos = _Last; + } + break; + } + + _Estimated_width += _Character_width; + if (_Estimated_width > _Max_width && _Max_width >= 0) { + // with this character, we exceed the maximum length + _Estimated_width -= _Character_width; + break; + } + + _Pos += _Units; + } + + _Width = _Estimated_width; + return _Pos; +} + template _NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs, locale) { @@ -2008,14 +2230,15 @@ _NODISCARD _OutputIt _Fmt_write( throw format_error("string cannot be localized"); } - auto _Printed_size = static_cast(_Value.size()); - - if (_Specs._Precision != -1 && _Printed_size > _Specs._Precision) { - _Printed_size = _Specs._Precision; + if (_Specs._Precision < 0 && _Specs._Width <= 0) { + return _Fmt_write(_STD move(_Out), _Value); } - return _Write_aligned(_STD move(_Out), _Printed_size, _Specs, _Align::_Left, [=](_OutputIt _Out) { - return _Fmt_write(_STD move(_Out), _Value.substr(size_t{0}, static_cast(_Printed_size))); + int _Width = _Specs._Precision; + const _CharT* _Last = _Measure_string_prefix(_Value, _Width); + + return _Write_aligned(_STD move(_Out), _Width, _Specs, _Align::_Left, [=](_OutputIt _Out) { + return _Fmt_write(_STD move(_Out), basic_string_view<_CharT>{_Value.data(), _Last}); }); } @@ -2124,7 +2347,7 @@ struct _Formatter_base { _Specs_checker<_Dynamic_specs_handler<_Pc>> _Handler(_Dynamic_specs_handler<_Pc>(_Specs, _ParseCtx), _ArgType); const auto _It = _Parse_format_specs(_ParseCtx._Unchecked_begin(), _ParseCtx._Unchecked_end(), _Handler); if (_It != _ParseCtx._Unchecked_end() && *_It != '}') { - throw format_error("Mising '}' in format string."); + throw format_error("Missing '}' in format string."); } return _ParseCtx.begin() + (_It - _ParseCtx._Unchecked_begin()); } diff --git a/stl/inc/xutility b/stl/inc/xutility index 3b70a2969f2..6d0c570ed3d 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -1013,7 +1013,13 @@ using indirect_result_t = invoke_result_t<_Fn, iter_reference_t<_Its>...>; template _Proj> struct projected { using value_type = remove_cvref_t>; +#if defined(__clang__) || defined(__EDG__) indirect_result_t<_Proj&, _It> operator*() const; +#else // ^^^ no workaround / workaround vvv + indirect_result_t<_Proj&, _It> operator*() const { + _CSTD abort(); // TRANSITION, VSO-1308657 + } +#endif // ^^^ workaround ^^^ }; #ifdef __clang__ @@ -4237,14 +4243,16 @@ namespace ranges { template using copy_result = in_out_result<_In, _Out>; + // VARIABLE ranges::copy // clang-format off template _Se, weakly_incrementable _Out> requires indirectly_copyable<_It, _Out> _NODISCARD constexpr copy_result<_It, _Out> _Copy_unchecked(_It _First, _Se _Last, _Out _Result) { + // clang-format on if constexpr (_Ptr_copy_cat<_It, _Out>::_Trivially_copyable && sized_sentinel_for<_Se, _It>) { if (!_STD is_constant_evaluated()) { auto _Final = _RANGES next(_First, _STD move(_Last)); - _Result = _Copy_memmove(_STD move(_First), _Final, _STD move(_Result)); + _Result = _Copy_memmove(_STD move(_First), _Final, _STD move(_Result)); return {_STD move(_Final), _STD move(_Result)}; } } @@ -4255,7 +4263,35 @@ namespace ranges { return {_STD move(_First), _STD move(_Result)}; } - // clang-format on + + class _Copy_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, weakly_incrementable _Out> + requires indirectly_copyable<_It, _Out> + constexpr copy_result<_It, _Out> operator()(_It _First, _Se _Last, _Out _Result) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _RANGES _Copy_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _STD move(_Result)); + _Seek_wrapped(_First, _STD move(_UResult.in)); + return {_STD move(_First), _STD move(_UResult.out)}; + } + + template + requires indirectly_copyable, _Out> + constexpr copy_result, _Out> operator()(_Rng&& _Range, _Out _Result) const { + auto _First = _RANGES begin(_Range); + auto _UResult = + _RANGES _Copy_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), _STD move(_Result)); + _Seek_wrapped(_First, _STD move(_UResult.in)); + return {_STD move(_First), _STD move(_UResult.out)}; + } + // clang-format on + }; + + inline constexpr _Copy_fn copy{_Not_quite_object::_Construct_tag{}}; } // namespace ranges #endif // __cpp_lib_concepts @@ -5300,11 +5336,13 @@ _NODISCARD _FwdIt find(_ExPo&& _Exec, _FwdIt _First, const _FwdIt _Last, const _ #ifdef __cpp_lib_concepts namespace ranges { + // VARIABLE ranges::find // clang-format off // concept-constrained for strict enforcement as it is used by several algorithms template _Se, class _Ty, class _Pj = identity> requires indirect_binary_predicate, const _Ty*> _NODISCARD constexpr _It _Find_unchecked(_It _First, const _Se _Last, const _Ty& _Val, _Pj _Proj = {}) { + // clang-format on if constexpr (_Memchr_in_find_is_safe<_It, _Ty> && sized_sentinel_for<_Se, _It> && same_as<_Pj, identity>) { if (!_STD is_constant_evaluated()) { if (!_Within_limits(_First, _Val)) { @@ -5312,8 +5350,8 @@ namespace ranges { } const auto _First_ptr = _STD to_address(_First); - const auto _Result = static_cast>*>(_CSTD memchr(_First_ptr, - static_cast(_Val), static_cast(_Last - _First))); + const auto _Result = static_cast>*>( + _CSTD memchr(_First_ptr, static_cast(_Val), static_cast(_Last - _First))); if (_Result) { if constexpr (is_pointer_v<_It>) { return _Result; @@ -5334,7 +5372,38 @@ namespace ranges { return _First; } - // clang-format on + + class _Find_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, class _Ty, class _Pj = identity> + requires indirect_binary_predicate, const _Ty*> + _NODISCARD constexpr _It operator()(_It _First, _Se _Last, const _Ty& _Val, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _RANGES _Find_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Val, _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult)); + return _First; + } + + template + requires indirect_binary_predicate, _Pj>, const _Ty*> + _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()( + _Rng&& _Range, const _Ty& _Val, _Pj _Proj = {}) const { + auto _First = _RANGES begin(_Range); + auto _UResult = + _RANGES _Find_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), _Val, _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult)); + return _First; + } + // clang-format on + }; + + inline constexpr _Find_fn find{_Not_quite_object::_Construct_tag{}}; } // namespace ranges #endif // __cpp_lib_concepts diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 7d8e6e10456..4aaf3288dd4 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -14,8 +14,6 @@ using namespace std; using namespace chrono; -#ifndef __clang__ // TRANSITION, LLVM-48606 - // copied from the string_view tests template struct choose_literal; // not defined @@ -49,37 +47,37 @@ struct testing_callbacks { vector<_Chrono_specs>& expected_chrono_specs; size_t curr_index = 0; - constexpr void _On_align(_Align aln) { + void _On_align(_Align aln) { assert(aln == expected_alignment); } - constexpr void _On_fill(basic_string_view str_view) { + void _On_fill(basic_string_view str_view) { assert(str_view == expected_fill); } - constexpr void _On_width(int width) { + void _On_width(int width) { assert(width == expected_width); } - constexpr void _On_dynamic_width(size_t id) { + void _On_dynamic_width(size_t id) { assert(id == expected_dynamic_width); } - constexpr void _On_dynamic_width(_Auto_id_tag) { + void _On_dynamic_width(_Auto_id_tag) { assert(expected_auto_dynamic_width); } - constexpr void _On_precision(int pre) { + void _On_precision(int pre) { assert(pre == expected_precision); } - constexpr void _On_dynamic_precision(size_t id) { + void _On_dynamic_precision(size_t id) { assert(id == expected_dynamic_precision); } - constexpr void _On_dynamic_precision(_Auto_id_tag) { + void _On_dynamic_precision(_Auto_id_tag) { assert(expected_auto_dynamic_precision); } - constexpr void _On_conversion_spec(CharT mod, CharT type) { + void _On_conversion_spec(CharT mod, CharT type) { assert(static_cast(mod) == expected_chrono_specs[curr_index]._Modifier); assert(static_cast(type) == expected_chrono_specs[curr_index]._Type); assert(expected_chrono_specs[curr_index]._Lit_char == CharT{0}); // not set ++curr_index; } - constexpr void _On_lit_char(CharT ch) { + void _On_lit_char(CharT ch) { assert(ch == expected_chrono_specs[curr_index]._Lit_char); assert(expected_chrono_specs[curr_index]._Modifier == '\0'); // not set assert(expected_chrono_specs[curr_index]._Type == '\0'); // not set @@ -88,8 +86,8 @@ struct testing_callbacks { }; template -constexpr void test_parse_helper(const CharT* (*func)(const CharT*, const CharT*, callback_type&&), - basic_string_view view, bool err_expected = false, +void test_parse_helper(const CharT* (*func)(const CharT*, const CharT*, callback_type&&), basic_string_view view, + bool err_expected = false, typename basic_string_view::size_type expected_end_position = basic_string_view::npos, callback_type&& callbacks = {}) { try { @@ -104,7 +102,7 @@ constexpr void test_parse_helper(const CharT* (*func)(const CharT*, const CharT* } template -constexpr bool test_parse_conversion_spec() { +bool test_parse_conversion_spec() { auto parse_conv_spec_fn = _Parse_conversion_specs>; using view_typ = basic_string_view; using chrono_spec = _Chrono_specs; @@ -126,19 +124,17 @@ constexpr bool test_parse_conversion_spec() { vector v2{{._Modifier = 'O', ._Type = 'd'}}; test_parse_helper(parse_conv_spec_fn, s2, false, view_typ::npos, {.expected_chrono_specs = v2}); - if (!is_constant_evaluated()) { - vector v{}; - test_parse_helper(parse_conv_spec_fn, s3, true, view_typ::npos, {.expected_chrono_specs = v}); - test_parse_helper(parse_conv_spec_fn, s4, true, view_typ::npos, {.expected_chrono_specs = v}); - test_parse_helper(parse_conv_spec_fn, s5, true, view_typ::npos, {.expected_chrono_specs = v}); - test_parse_helper(parse_conv_spec_fn, s6, true, view_typ::npos, {.expected_chrono_specs = v}); - } + vector v{}; + test_parse_helper(parse_conv_spec_fn, s3, true, view_typ::npos, {.expected_chrono_specs = v}); + test_parse_helper(parse_conv_spec_fn, s4, true, view_typ::npos, {.expected_chrono_specs = v}); + test_parse_helper(parse_conv_spec_fn, s5, true, view_typ::npos, {.expected_chrono_specs = v}); + test_parse_helper(parse_conv_spec_fn, s6, true, view_typ::npos, {.expected_chrono_specs = v}); return true; } template -constexpr bool test_parse_chrono_format_specs() { +bool test_parse_chrono_format_specs() { auto parse_chrono_format_specs_fn = _Parse_chrono_format_specs>; using view_typ = basic_string_view; using chrono_spec = _Chrono_specs; @@ -185,11 +181,9 @@ constexpr bool test_parse_chrono_format_specs() { .expected_precision = 4, .expected_chrono_specs = v5}); - if (!is_constant_evaluated()) { - vector v{{._Type = 'H'}}; // we don't throw a format_error until we parse the %H - test_parse_helper(parse_chrono_format_specs_fn, s6, true, view_typ::npos, {.expected_chrono_specs = v}); - test_parse_helper(parse_chrono_format_specs_fn, s7, true, view_typ::npos, {.expected_chrono_specs = v}); - } + vector v{{._Type = 'H'}}; // we don't throw a format_error until we parse the %H + test_parse_helper(parse_chrono_format_specs_fn, s6, true, view_typ::npos, {.expected_chrono_specs = v}); + test_parse_helper(parse_chrono_format_specs_fn, s7, true, view_typ::npos, {.expected_chrono_specs = v}); return true; } @@ -197,17 +191,7 @@ constexpr bool test_parse_chrono_format_specs() { int main() { test_parse_conversion_spec(); test_parse_conversion_spec(); - static_assert(test_parse_conversion_spec()); - static_assert(test_parse_conversion_spec()); test_parse_chrono_format_specs(); test_parse_chrono_format_specs(); - static_assert(test_parse_chrono_format_specs()); - static_assert(test_parse_chrono_format_specs()); } - -#else // ^^^ !__clang__ / __clang__ vvv - -int main() {} - -#endif // __clang__ diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 423a0d6cb85..6beefe52c75 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -475,7 +475,7 @@ void test_fill_and_align() { } template -void test_intergal_specs() { +void test_integral_specs() { assert(format(STR("{:}"), integral{0}) == STR("0")); // Sign @@ -900,10 +900,10 @@ void test_string_specs() { template void test_spec_replacement_field() { - test_intergal_specs(); - test_intergal_specs(); - test_intergal_specs(); - test_intergal_specs(); + test_integral_specs(); + test_integral_specs(); + test_integral_specs(); + test_integral_specs(); test_bool_specs(); test_char_specs(); test_float_specs(); @@ -953,6 +953,60 @@ void test_size() { test_size_helper(8, STR("{:8}"), STR("scully")); } +void test_multibyte_format_strings() { + { + setlocale(LC_ALL, ".932"); + const auto s = + "\x93\xfa\x96{\x92\x6e\x90}"sv; // Note the use of `{` and `}` as continuation bytes (from GH-1576) + assert(format(s) == s); + + assert(format("{:.2}", s) == "\x93\xfa"sv); + assert(format("{:4.2}", s) == "\x93\xfa "sv); + + assert(format("{:<4.2}", s) == "\x93\xfa "sv); + assert(format("{:^4.2}", s) == " \x93\xfa "sv); + assert(format("{:>4.2}", s) == " \x93\xfa"sv); + + assert(format("{:\x90}<4.2}", s) == "\x93\xfa\x90}\x90}"sv); + assert(format("{:\x90}^4.2}", s) == "\x90}\x93\xfa\x90}"sv); + assert(format("{:\x90}>4.2}", s) == "\x90}\x90}\x93\xfa"sv); + + assert(format("{:.3}", s) == "\x93\xfa"sv); + assert(format("{:4.3}", s) == "\x93\xfa "sv); + + assert(format("{:<4.3}", s) == "\x93\xfa "sv); + assert(format("{:^4.3}", s) == " \x93\xfa "sv); + assert(format("{:>4.3}", s) == " \x93\xfa"sv); + + assert(format("{:\x90}<4.3}", s) == "\x93\xfa\x90}\x90}"sv); + assert(format("{:\x90}^4.3}", s) == "\x90}\x93\xfa\x90}"sv); + assert(format("{:\x90}>4.3}", s) == "\x90}\x90}\x93\xfa"sv); + } + +#ifndef MSVC_INTERNAL_TESTING // TRANSITION, Windows on Contest VMs understand ".UTF-8" codepage + { + setlocale(LC_ALL, ".UTF-8"); + // Filling with footballs ("\xf0\x9f\x8f\x88" is U+1F3C8 AMERICAN FOOTBALL) + assert(format("{:\xf0\x9f\x8f\x88>4}"sv, 42) == "\xf0\x9f\x8f\x88\xf0\x9f\x8f\x88\x34\x32"); + + assert(format("{:\xf0\x9f\x8f\x88<4.2}", "1") == "\x31\xf0\x9f\x8f\x88\xf0\x9f\x8f\x88\xf0\x9f\x8f\x88"sv); + assert(format("{:\xf0\x9f\x8f\x88^4.2}", "1") == "\xf0\x9f\x8f\x88\x31\xf0\x9f\x8f\x88\xf0\x9f\x8f\x88"sv); + assert(format("{:\xf0\x9f\x8f\x88>4.2}", "1") == "\xf0\x9f\x8f\x88\xf0\x9f\x8f\x88\xf0\x9f\x8f\x88\x31"sv); + } + + { + setlocale(LC_ALL, ".UTF-8"); + try { + (void) format("{:\x9f\x8f\x88<10}"sv, 42); // Bad fill character encoding: missing lead byte before \x9f + assert(false); + } catch (const format_error&) { + } + } +#endif // MSVC_INTERNAL_TESTING + + setlocale(LC_ALL, nullptr); +} + int main() { test_simple_formatting(); test_simple_formatting(); @@ -975,5 +1029,7 @@ int main() { test_size(); test_size(); + test_multibyte_format_strings(); + return 0; } diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index d513ba4d914..9a7f62083d7 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -62,6 +62,7 @@ struct testing_callbacks { bool expected_zero = false; bool expected_localized = false; CharT expected_type = '\0'; + constexpr void _On_align(_Align aln) { assert(aln == expected_alignment); } @@ -111,8 +112,8 @@ struct testing_arg_id_callbacks { }; template -constexpr void test_parse_helper(const CharT* (*func)(const CharT*, const CharT*, callback_type&&), - basic_string_view view, bool err_expected = false, +void test_parse_helper(const CharT* (*func)(const CharT*, const CharT*, callback_type&&), basic_string_view view, + bool err_expected = false, typename basic_string_view::size_type expected_end_position = basic_string_view::npos, callback_type&& callbacks = {}) { try { @@ -127,7 +128,7 @@ constexpr void test_parse_helper(const CharT* (*func)(const CharT*, const CharT* } template -constexpr bool test_parse_align() { +bool test_parse_align() { auto parse_align_fn = _Parse_align>; using view_typ = basic_string_view; @@ -141,6 +142,7 @@ constexpr bool test_parse_align() { {.expected_alignment = _Align::_Right, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*"))}); test_parse_helper(parse_align_fn, s3, false, view_typ::npos, {.expected_alignment = _Align::_Center, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*"))}); + if constexpr (same_as) { // This is a CJK character where the least significant byte is the same as ascii '>', // libfmt and initial drafts of narrowed characters when parsing alignments, causing @@ -148,13 +150,49 @@ constexpr bool test_parse_align() { // an alignment specifier. auto s4 = L"*\x343E"sv; test_parse_helper(parse_align_fn, s4, false, view_typ::npos, {.expected_fill = L"*"sv}); + + // test multi-code-unit fill characters + { + test_parse_helper(parse_align_fn, L"\U0001F3C8X"sv, false, 3, + {.expected_alignment = _Align::_Right, .expected_fill = L"\U0001F3C8"sv}); + test_parse_helper(parse_align_fn, L"\U0001F3C8^X"sv, false, 3, + {.expected_alignment = _Align::_Center, .expected_fill = L"\U0001F3C8"sv}); + } + } else { + // test multibyte fill characters + { + setlocale(LC_ALL, ".932"); + test_parse_helper(parse_align_fn, "\x93\xfaX"sv, false, 3, + {.expected_alignment = _Align::_Right, .expected_fill = "\x96\x7b"sv}); + test_parse_helper(parse_align_fn, "\x92\x6e^X"sv, false, 3, + {.expected_alignment = _Align::_Center, .expected_fill = "\x92\x6e"sv}); + } + +#ifndef MSVC_INTERNAL_TESTING // TRANSITION, Windows on Contest VMs understand ".UTF-8" codepage + { + setlocale(LC_ALL, ".UTF-8"); + // "\xf0\x9f\x8f\x88" is U+1F3C8 AMERICAN FOOTBALL + test_parse_helper(parse_align_fn, "\xf0\x9f\x8f\x88X"sv, false, 5, + {.expected_alignment = _Align::_Right, .expected_fill = "\xf0\x9f\x8f\x88"sv}); + test_parse_helper(parse_align_fn, "\xf0\x9f\x8f\x88^X"sv, false, 5, + {.expected_alignment = _Align::_Center, .expected_fill = "\xf0\x9f\x8f\x88"sv}); + } +#endif // MSVC_INTERNAL_TESTING + + setlocale(LC_ALL, nullptr); } return true; } template -constexpr bool test_parse_width() { +bool test_parse_width() { auto parse_width_fn = _Parse_width>; using view_typ = basic_string_view; @@ -175,7 +213,7 @@ constexpr bool test_parse_width() { } template -constexpr bool test_parse_arg_id() { +bool test_parse_arg_id() { auto parse_arg_id_fn = _Parse_arg_id; using view_typ = basic_string_view; // note that parse arg id starts with the arg id itself, not the { beginning of the @@ -207,7 +245,7 @@ constexpr bool test_parse_arg_id() { } template -constexpr bool test_parse_precision() { +bool test_parse_precision() { auto parse_pre_fn = _Parse_precision>; using view_typ = basic_string_view; @@ -239,7 +277,7 @@ constexpr bool test_parse_precision() { } template -constexpr bool test_parse_format_specs() { +bool test_parse_format_specs() { auto parse_format_specs_fn = _Parse_format_specs>; using view_typ = basic_string_view; @@ -305,28 +343,18 @@ constexpr bool test_specs_checker() { int main() { test_parse_align(); test_parse_align(); - static_assert(test_parse_align()); - static_assert(test_parse_align()); test_parse_arg_id(); test_parse_arg_id(); - static_assert(test_parse_arg_id()); - static_assert(test_parse_arg_id()); test_parse_width(); test_parse_width(); - static_assert(test_parse_width()); - static_assert(test_parse_width()); test_parse_precision(); test_parse_precision(); - static_assert(test_parse_precision()); - static_assert(test_parse_precision()); test_parse_format_specs(); test_parse_format_specs(); - static_assert(test_parse_format_specs()); - static_assert(test_parse_format_specs()); test_specs_setter(); test_specs_setter();