diff --git a/stl/inc/format b/stl/inc/format index 95851308862..b0b97ca0575 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1762,10 +1762,13 @@ template class _Specs_checker : public _Handler { private: _Basic_format_arg_type _Arg_type; - // we'll see this if we get a modifier that requires an integer presentation type - // for types that can have either integer or non-integer presentation types (charT or bool) + + // Set for the hash and sign modifiers. bool _Need_arithmetic_presentation_type = false; + // Set for the zero modifier. + bool _Need_arithmetic_or_pointer_presentation_type = false; + public: constexpr explicit _Specs_checker(const _Handler& _Handler_inst, const _Basic_format_arg_type _Arg_type_) : _Handler(_Handler_inst), _Arg_type(_Arg_type_) {} @@ -1776,6 +1779,12 @@ public: } } + constexpr void _Require_numeric_or_pointer_argument() const { + if (!_Is_arithmetic_fmt_type(_Arg_type) && _Arg_type != _Basic_format_arg_type::_Pointer_type) { + _Throw_format_error("Format specifier requires numeric or pointer argument."); + } + } + constexpr void _Check_precision() const { if (_Is_integral_fmt_type(_Arg_type) || _Arg_type == _Basic_format_arg_type::_Pointer_type) { _Throw_format_error("Precision not allowed for this argument type."); @@ -1802,8 +1811,8 @@ public: } constexpr void _On_zero() { - _Need_arithmetic_presentation_type = true; - _Require_numeric_argument(); + _Need_arithmetic_or_pointer_presentation_type = true; + _Require_numeric_or_pointer_argument(); _Handler::_On_zero(); } @@ -1869,6 +1878,7 @@ public: _Cat = _Presentation_type_category::_Floating; break; case 'p': + case 'P': _Cat = _Presentation_type_category::_Pointer; break; default: @@ -1960,7 +1970,18 @@ public: if (_Need_arithmetic_presentation_type && _Cat != _Presentation_type_category::_Integer && _Cat != _Presentation_type_category::_Floating) { - _Throw_format_error("Modifier requires an integer presentation type for bool"); + // N4971 [format.string.std]/5: "The sign option is only valid for arithmetic types + // other than charT and bool or when an integer presentation type is specified." + // N4971 [format.string.std]/7: "The # option [...] is valid for arithmetic types + // other than charT and bool or when an integer presentation type is specified, and not otherwise." + _Throw_format_error("Hash/sign modifier requires an arithmetic presentation type"); + } + + if (_Need_arithmetic_or_pointer_presentation_type && _Cat != _Presentation_type_category::_Integer + && _Cat != _Presentation_type_category::_Floating && _Cat != _Presentation_type_category::_Pointer) { + // N4971 [format.string.std]/8: "The 0 option is valid for arithmetic types + // other than charT and bool, pointer types, or when an integer presentation type is specified." + _Throw_format_error("Zero modifier requires an arithmetic or pointer presentation type"); } _Handler::_On_type(_Type); } @@ -3168,25 +3189,37 @@ _NODISCARD _OutputIt _Fmt_write( template _NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const void* const _Value, const _Basic_format_specs<_CharT>& _Specs, _Lazy_locale) { - _STL_INTERNAL_CHECK(_Specs._Type == '\0' || _Specs._Type == 'p'); + _STL_INTERNAL_CHECK(_Specs._Type == '\0' || _Specs._Type == 'p' || _Specs._Type == 'P'); _STL_INTERNAL_CHECK(_Specs._Sgn == _Fmt_sign::_None); _STL_INTERNAL_CHECK(!_Specs._Alt); _STL_INTERNAL_CHECK(_Specs._Precision == -1); - _STL_INTERNAL_CHECK(!_Specs._Leading_zero); _STL_INTERNAL_CHECK(!_Specs._Localized); - // Since the bit width of 0 is 0x0, special-case it instead of complicating the math even more. - int _Width = 3; - if (_Value != nullptr) { - // Compute the bit width of the pointer (i.e. how many bits it takes to be represented). - // Add 3 to the bit width so we always round up on the division. - // Divide that by the amount of bits a hexit represents (log2(16) = log2(2^4) = 4). - // Add 2 for the 0x prefix. - _Width = 2 + (_STD bit_width(reinterpret_cast(_Value)) + 3) / 4; + char _Buffer[2 * sizeof(void*)]; // 2 hexits per byte: 8 hexits for 32-bit, 16 hexits for 64-bit + const auto [_End, _Ec] = _STD to_chars(_Buffer, _STD end(_Buffer), reinterpret_cast(_Value), 16); + _STL_INTERNAL_CHECK(_Ec == errc{}); + const int _Width = 2 + static_cast(_End - _Buffer); + + _CharT _Prefix[2] = {_CharT{'0'}, _CharT{'x'}}; + if (_Specs._Type == 'P') { + _Prefix[1] = _CharT{'X'}; + _Buffer_to_uppercase(_Buffer, _End); + } + + const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Fmt_align::_None; + auto _Writer = [&](_OutputIt _Out) { + _Out = _RANGES copy(_Prefix, _Prefix + 2, _STD move(_Out)).out; + if (_Write_leading_zeroes && _Width < _Specs._Width) { + _Out = _RANGES fill_n(_STD move(_Out), _Specs._Width - _Width, _CharT{'0'}); + } + return _STD _Widen_and_copy<_CharT>(_Buffer, _End, _STD move(_Out)); + }; + + if (_Write_leading_zeroes) { + return _Writer(_STD move(_Out)); } - return _Write_aligned(_STD move(_Out), _Width, _Specs, _Fmt_align::_Right, - [=](_OutputIt _Out) { return _Fmt_write<_CharT>(_STD move(_Out), _Value); }); + return _STD _Write_aligned(_STD move(_Out), _Width, _Specs, _Fmt_align::_Right, _Writer); } template diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 40023ee9d51..6ec9b92a36f 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -296,6 +296,7 @@ // P2432R1 Fix istream_view // P2465R3 Standard Library Modules std And std.compat // P2508R1 basic_format_string, format_string, wformat_string +// P2510R3 Formatting Pointers // P2520R0 move_iterator Should Be A Random-Access Iterator // P2538R1 ADL-Proof projected // P2572R1 std::format Fill Character Allowances @@ -1751,7 +1752,7 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect #define __cpp_lib_erase_if 202002L #ifdef __cpp_lib_concepts -#define __cpp_lib_format 202207L +#define __cpp_lib_format 202304L #define __cpp_lib_format_uchar 202311L #define __cpp_lib_freestanding_ranges 202306L #endif // defined(__cpp_lib_concepts) diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 3f938e603a9..ad178e89318 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -294,12 +294,6 @@ std/language.support/support.limits/support.limits.general/charconv.version.comp std/utilities/charconv/charconv.syn/from_chars_result.operator_bool.pass.cpp FAIL std/utilities/charconv/charconv.syn/to_chars_result.operator_bool.pass.cpp FAIL -# P2510R3 Formatting Pointers -std/utilities/format/format.functions/format.locale.pass.cpp FAIL -std/utilities/format/format.functions/format.pass.cpp FAIL -std/utilities/format/format.functions/vformat.locale.pass.cpp FAIL -std/utilities/format/format.functions/vformat.pass.cpp FAIL - # P2587R3 Redefining to_string To Use to_chars std/language.support/support.limits/support.limits.general/string.version.compile.pass.cpp FAIL diff --git a/tests/std/test.lst b/tests/std/test.lst index ee4fa316ece..489f121d629 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -641,6 +641,7 @@ tests\P2474R2_views_repeat tests\P2474R2_views_repeat_death tests\P2494R2_move_only_range_adaptors tests\P2505R5_monadic_functions_for_std_expected +tests\P2510R3_text_formatting_pointers tests\P2517R1_apply_conditional_noexcept tests\P2538R1_adl_proof_std_projected tests\P2609R3_relaxing_ranges_just_a_smidge diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 96c976e4be2..7dea640f249 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -991,7 +991,7 @@ void test_pointer_specs() { throw_helper(STR("{:#}"), nullptr); // Leading zero - throw_helper(STR("{:0}"), nullptr); + assert(format(STR("{:05}"), nullptr) == STR("0x000")); // Width assert(format(STR("{:5}"), nullptr) == STR(" 0x0")); @@ -1350,10 +1350,10 @@ void libfmt_formatter_test_zero_flag() { assert(format(STR("{0:05}"), 42ull) == STR("00042")); assert(format(STR("{0:07}"), -42.0) == STR("-000042")); assert(format(STR("{0:07}"), -42.0l) == STR("-000042")); + assert(format(STR("{0:05}"), reinterpret_cast(0x42)) == STR("0x042")); throw_helper(STR("{0:0"), 'c'); throw_helper(STR("{0:05}"), 'c'); throw_helper(STR("{0:05}"), STR("abc")); - throw_helper(STR("{0:05}"), reinterpret_cast(0x42)); } template diff --git a/tests/std/tests/P2510R3_text_formatting_pointers/env.lst b/tests/std/tests/P2510R3_text_formatting_pointers/env.lst new file mode 100644 index 00000000000..d6d824b5879 --- /dev/null +++ b/tests/std/tests/P2510R3_text_formatting_pointers/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_20_matrix.lst diff --git a/tests/std/tests/P2510R3_text_formatting_pointers/test.cpp b/tests/std/tests/P2510R3_text_formatting_pointers/test.cpp new file mode 100644 index 00000000000..e7d37a270c6 --- /dev/null +++ b/tests/std/tests/P2510R3_text_formatting_pointers/test.cpp @@ -0,0 +1,301 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define STR(Str) TYPED_LITERAL(CharT, Str) + +using namespace std; + +template +basic_string pointer_to_string(const void* ptr) { + constexpr size_t hexits = 2 * sizeof(void*); + char buffer[hexits]; + auto [end, ec] = to_chars(buffer, buffer + hexits, reinterpret_cast(ptr), 16); + assert(ec == errc{}); + basic_string out; + ranges::transform(buffer, end, back_inserter(out), [](char c) { return static_cast(c); }); + return out; +} + +template +basic_string string_to_uppercase(const basic_string& str) { + basic_string out; + ranges::transform(str, back_inserter(out), [](auto c) { + if constexpr (same_as) { + return static_cast(toupper(static_cast(c))); + } else if constexpr (same_as) { + return static_cast(towupper(static_cast(c))); + } + }); + return out; +} + +template + requires indirect_binary_predicate, const T*> +constexpr bool all_equal_to(R&& r, const T& val) { + return ranges::all_of(r, [&val](const auto& elem) { return val == elem; }); +} + +static_assert(all_equal_to(array{1, 1, 1, 1}, 1)); +static_assert(!all_equal_to(array{1, 1, 1, 1}, 2)); +static_assert(!all_equal_to(array{1, 1, 1, 2}, 1)); + +template class Fmt> +void check_pointer_formatter() { + Fmt fmt; + const int variable = 0; + const void* const ptr = &variable; + const auto rep = pointer_to_string(ptr); + const auto lowercase_rep = STR("0x") + rep; + const auto uppercase_rep = STR("0X") + string_to_uppercase(rep); + + { // type == '\0' + assert(fmt(STR("{}"), ptr) == lowercase_rep); + assert(fmt(STR("{:}"), ptr) == lowercase_rep); + assert(fmt(STR("{0}"), ptr) == lowercase_rep); + assert(fmt(STR("{0:}"), ptr) == lowercase_rep); + } + + { // align only + assert(fmt(STR("{:<}"), ptr) == lowercase_rep); + assert(fmt(STR("{:^}"), ptr) == lowercase_rep); + assert(fmt(STR("{:>}"), ptr) == lowercase_rep); + } + + { // fill-and-align only + assert(fmt(STR("{:_<}"), ptr) == lowercase_rep); + assert(fmt(STR("{:-^}"), ptr) == lowercase_rep); + assert(fmt(STR("{:=>}"), ptr) == lowercase_rep); + } + + { // type == 'p' || type == 'P' + assert(fmt(STR("{:p}"), ptr) == lowercase_rep); + assert(fmt(STR("{:P}"), ptr) == uppercase_rep); + } + + { // leading zero and type + assert(fmt(STR("{:0}"), ptr) == lowercase_rep); + assert(fmt(STR("{:0p}"), ptr) == lowercase_rep); + assert(fmt(STR("{:0P}"), ptr) == uppercase_rep); + } + + { // width only + const int pad = static_cast(25 - lowercase_rep.size()); + const auto str = fmt(STR("{:25}"), ptr); + assert(all_equal_to(views::take(str, pad), STR(' '))); + assert(ranges::equal(views::drop(str, pad), lowercase_rep)); + } + + { // width && align + const auto str = fmt(STR("{:<31}"), ptr); + assert(ranges::equal(views::take(str, static_cast(lowercase_rep.size())), lowercase_rep)); + assert(all_equal_to(views::drop(str, static_cast(lowercase_rep.size())), ' ')); + } + + { // width && fill-and-align (2) + const int w = 22; + const int pad = static_cast(w - lowercase_rep.size()); + const auto str = fmt(STR("{:*^{}}"), ptr, w); + basic_string_view v{str}; + + const int left_pad = pad / 2; + assert(all_equal_to(views::take(v, left_pad), STR('*'))); + v.remove_prefix(static_cast(left_pad)); + assert(v.starts_with(lowercase_rep)); + v.remove_prefix(lowercase_rep.size()); + assert(all_equal_to(v, STR('*'))); + } + + { // leading zero && width && type == 'p' + const int w = 35; + const auto str = fmt(STR("{:0{}p}"), ptr, w); + basic_string_view v{str}; + assert(v.starts_with(STR("0x"))); + v.remove_prefix(2); + const int zero_count = w - static_cast(lowercase_rep.size()); + assert(all_equal_to(views::take(v, zero_count), STR('0'))); + v.remove_prefix(static_cast(zero_count)); + assert(ranges::equal(v, views::drop(lowercase_rep, 2))); + } + + { // leading zero && width && type == 'P' + const int w = 39; + const auto str = fmt(STR("{1:0{0}P}"), w, ptr); + basic_string_view v{str}; + assert(v.starts_with(STR("0X"))); + v.remove_prefix(2); + const int zero_count = w - static_cast(uppercase_rep.size()); + assert(all_equal_to(views::take(v, zero_count), STR('0'))); + v.remove_prefix(static_cast(zero_count)); + assert(ranges::equal(v, views::drop(uppercase_rep, 2))); + } + + { // align && leading zero (should have no effect) && width && type == 'p' + const int w = 42; + const int pad = static_cast(w - lowercase_rep.size()); + const auto str = fmt(STR("{:^0{}p}"), ptr, w); + const int left_pad = pad / 2; + basic_string_view v{str}; + assert(all_equal_to(views::take(v, left_pad), STR(' '))); + v.remove_prefix(static_cast(left_pad)); + assert(v.starts_with(lowercase_rep)); + v.remove_prefix(lowercase_rep.size()); + assert(all_equal_to(v, STR(' '))); + } + + { // fill-and-align && leading zero (should have no effect) && width && type == 'P' + const int w = 44; + const int pad = static_cast(w - uppercase_rep.size()); + const auto str = fmt(STR("{1:#>0{0}P}"), w, ptr); + basic_string_view v{str}; + assert(all_equal_to(views::take(v, pad), STR('#'))); + v.remove_prefix(static_cast(pad)); + assert(ranges::equal(v, uppercase_rep)); + } +} + +template class Fmt> +void check_nullptr_t_formatter() { + Fmt fmt; + + { // type == '\0' + assert(fmt(STR("{}"), nullptr) == STR("0x0")); + assert(fmt(STR("{:}"), nullptr) == STR("0x0")); + assert(fmt(STR("{0}"), nullptr) == STR("0x0")); + assert(fmt(STR("{0:}"), nullptr) == STR("0x0")); + } + + { // align only + assert(fmt(STR("{:<}"), nullptr) == STR("0x0")); + assert(fmt(STR("{:^}"), nullptr) == STR("0x0")); + assert(fmt(STR("{:>}"), nullptr) == STR("0x0")); + } + + { // fill-and-align only + assert(fmt(STR("{:_<}"), nullptr) == STR("0x0")); + assert(fmt(STR("{:-^}"), nullptr) == STR("0x0")); + assert(fmt(STR("{:=>}"), nullptr) == STR("0x0")); + } + + { // type == 'p' || type == 'P' + assert(fmt(STR("{:p}"), nullptr) == STR("0x0")); + assert(fmt(STR("{:P}"), nullptr) == STR("0X0")); + } + + { // leading zero and type + assert(fmt(STR("{:0}"), nullptr) == STR("0x0")); + assert(fmt(STR("{:0p}"), nullptr) == STR("0x0")); + assert(fmt(STR("{:0P}"), nullptr) == STR("0X0")); + } + + { // width only + assert(fmt(STR("{:5}"), nullptr) == STR(" 0x0")); + assert(fmt(STR("{:7}"), nullptr) == STR(" 0x0")); + assert(fmt(STR("{:11}"), nullptr) == STR(" 0x0")); + } + + { // width && align + assert(fmt(STR("{:<5}"), nullptr) == STR("0x0 ")); + assert(fmt(STR("{:^7}"), nullptr) == STR(" 0x0 ")); + assert(fmt(STR("{:>11}"), nullptr) == STR(" 0x0")); + } + + { // width && fill-and-align + assert(fmt(STR("{:#<5}"), nullptr) == STR("0x0##")); + assert(fmt(STR("{:*^7}"), nullptr) == STR("**0x0**")); + assert(fmt(STR("{:=>11}"), nullptr) == STR("========0x0")); + } + + { // leading zero && width && type == '\0' + assert(fmt(STR("{:05}"), nullptr) == STR("0x000")); + assert(fmt(STR("{:07}"), nullptr) == STR("0x00000")); + assert(fmt(STR("{:011}"), nullptr) == STR("0x000000000")); + } + + { // leading zero && width && type == 'p' + assert(fmt(STR("{:05p}"), nullptr) == STR("0x000")); + assert(fmt(STR("{:07p}"), nullptr) == STR("0x00000")); + assert(fmt(STR("{:011p}"), nullptr) == STR("0x000000000")); + } + + { // leading zero && width && type == 'P' + assert(fmt(STR("{:05P}"), nullptr) == STR("0X000")); + assert(fmt(STR("{:07P}"), nullptr) == STR("0X00000")); + assert(fmt(STR("{:011P}"), nullptr) == STR("0X000000000")); + } + + { // align && leading zero (should have no effect) && width && type in ('\0', 'p', 'P') + assert(fmt(STR("{:<05}"), nullptr) == STR("0x0 ")); + assert(fmt(STR("{:^07p}"), nullptr) == STR(" 0x0 ")); + assert(fmt(STR("{:>011P}"), nullptr) == STR(" 0X0")); + } + + { // fill-and-align && leading zero (should have no effect) && width && type in ('\0', 'p', 'P') + assert(fmt(STR("{:!<05}"), nullptr) == STR("0x0!!")); + assert(fmt(STR("{:@^07p}"), nullptr) == STR("@@0x0@@")); + assert(fmt(STR("{:#>011P}"), nullptr) == STR("########0X0")); + } +} + +template +void check_invalid_format_spec() { + ExpectFormatError fmt; + PtrType ptr = nullptr; + + { // damaged fields + fmt(STR("{"), ptr); + fmt(STR("{}}"), ptr); + fmt(STR("{{}"), ptr); + fmt(STR("{:}}"), ptr); + } + + { // sign || '#' || precision || 'L' + fmt(STR("{:+}"), ptr); + fmt(STR("{:-}"), ptr); + fmt(STR("{: }"), ptr); + fmt(STR("{:#}"), ptr); + fmt(STR("{:.2}"), ptr); + fmt(STR("{:L}"), ptr); + } + + { // mixed invalid specs + fmt(STR("{:+#}"), ptr); + fmt(STR("{:-.3}"), ptr); + fmt(STR("{: #}"), ptr); + fmt(STR("{:#.4}"), ptr); + fmt(STR("{:.3o}"), ptr); + fmt(STR("{:X}"), ptr); + fmt(STR("{:invalid for sure}"), ptr); + } +} + +template +void test() { + check_pointer_formatter(); + check_pointer_formatter(); + check_nullptr_t_formatter(); + check_nullptr_t_formatter(); + check_invalid_format_spec(); + check_invalid_format_spec(); +} + +int main() { + test(); + test(); +} diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index 90c296a3258..7d8d9901dec 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -356,7 +356,7 @@ STATIC_ASSERT(__cpp_lib_filesystem == 201703L); #endif #ifdef __cpp_lib_concepts -STATIC_ASSERT(__cpp_lib_format == 202207L); +STATIC_ASSERT(__cpp_lib_format == 202304L); #elif defined(__cpp_lib_format) #error __cpp_lib_format is defined #endif