From b10d526404456d6ba82d4d633a48d4da20e65551 Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Tue, 22 Sep 2020 14:27:18 -0700 Subject: [PATCH 01/94] feature/format arg_id parsing, align parsing, width parsing, skeleton classes (#1232) * add format header * add tests to build * add format_arg_value and custom_value. * more format parser * parse align * fill/align parsing tests. * arg_id and width * tests for arg_id, quite basic right now * correct a spelling error * add to other required files * resolve some review comments * respond to review comments * start conversion to string_view * some tests for width * enable wchar_t * constexprify tests. * tests for non-parsing is todo * forgot a bit of named args that needed removal. * remove a requires clause and use brief syntax. * newlines * use concepts_matrix. * some review comments were hiding from me * fix wchar_t misparse bug * Apply suggestions from code review Co-authored-by: Casey Carter * Apply suggestions from code review Co-authored-by: Casey Carter * move test coverage todo * more review comments * fix test string numbering * comment on narrowing test case * fix minor issues Co-authored-by: Charles Barto Co-authored-by: Casey Carter --- stl/CMakeLists.txt | 1 + stl/inc/__msvc_all_public_headers.hpp | 1 + stl/inc/format | 357 ++++++++++++++++++ tests/std/test.lst | 2 + .../env.lst | 4 + .../test.cpp | 12 + .../P0645R10_text_formatting_parsing/env.lst | 4 + .../P0645R10_text_formatting_parsing/test.cpp | 173 +++++++++ .../include_each_header_alone_matrix.lst | 1 + 9 files changed, 555 insertions(+) create mode 100644 stl/inc/format create mode 100644 tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst create mode 100644 tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp create mode 100644 tests/std/tests/P0645R10_text_formatting_parsing/env.lst create mode 100644 tests/std/tests/P0645R10_text_formatting_parsing/test.cpp diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index 74c5f383bfc..e6f1d9894d9 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -140,6 +140,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/experimental/unordered_set ${CMAKE_CURRENT_LIST_DIR}/inc/experimental/vector ${CMAKE_CURRENT_LIST_DIR}/inc/filesystem + ${CMAKE_CURRENT_LIST_DIR}/inc/format ${CMAKE_CURRENT_LIST_DIR}/inc/forward_list ${CMAKE_CURRENT_LIST_DIR}/inc/fstream ${CMAKE_CURRENT_LIST_DIR}/inc/functional diff --git a/stl/inc/__msvc_all_public_headers.hpp b/stl/inc/__msvc_all_public_headers.hpp index 3ba146c97bc..8c4d92c0ea5 100644 --- a/stl/inc/__msvc_all_public_headers.hpp +++ b/stl/inc/__msvc_all_public_headers.hpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include diff --git a/stl/inc/format b/stl/inc/format new file mode 100644 index 00000000000..addbe13cdf7 --- /dev/null +++ b/stl/inc/format @@ -0,0 +1,357 @@ +// format standard header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef _FORMAT_ +#define _FORMAT_ +#include +#if _STL_COMPILER_PREPROCESSOR +#ifndef __cpp_lib_concepts +#pragma message("The contents of are available only with C++20 concepts support.") +#else // ^^^ !defined(__cpp_lib_concepts) / defined(__cpp_lib_concepts) vvv + +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +_STD_BEGIN + +class format_error : public runtime_error { + using runtime_error::runtime_error; +}; + +enum class _Align { _None, _Left, _Right, _Center }; + +struct _Auto_id_tag {}; + +// clang-format off +template +concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln) { + { _At._On_align(_Aln) } -> same_as; + { _At._On_fill(_Sv) } -> same_as; + { _At._On_width(_STD declval()) } -> same_as; + { _At._On_dynamic_width(_STD declval()) } -> same_as; + { _At._On_dynamic_width(_STD declval<_Auto_id_tag>()) } -> same_as; +}; +template +concept _Parse_arg_id_callbacks = requires(_Ty _At) { + { _At._On_auto_id() } -> same_as; + { _At._On_manual_id(_STD declval()) } -> same_as; +}; +// clang-format on + +// we need to implement this ourselves because from_chars does not work with wide characters +template +constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _CharT* _End, int& _Integer) { + _STL_INTERNAL_CHECK(_Begin != _End && '0' <= *_Begin && *_Begin <= '9'); + unsigned int _Value = 0; + constexpr unsigned int _Max_int = static_cast((numeric_limits::max)()); + constexpr unsigned int _Big_int = _Max_int / 10; + + do { + if (_Value > _Big_int) { + _Value = _Max_int + 1; + break; + } + _Value = _Value * 10 + static_cast(*_Begin - '0'); + ++_Begin; + } while (_Begin != _End && '0' <= *_Begin && *_Begin <= '9'); + if (_Value > _Max_int) { + throw format_error("Number is too big"); + } + _Integer = static_cast(_Value); + return _Begin; +} + +template _Callbacks_type> +constexpr const _CharT* _Parse_arg_id(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + _STL_INTERNAL_CHECK(_Begin != _End); + _CharT _Ch = *_Begin; + // No id provided, format string is using automatic indexing. + if (_Ch == '}' || _Ch == ':') { + _Callbacks._On_auto_id(); + return _Begin; + } + if (_Ch >= '0' && _Ch <= '9') { + int _Index = 0; + // arg_id is not allowed to have any leading zeros, but is allowed to be + // equal to zero (but not '00'). So if _Ch is zero we skip the parsing, leave + // _Index set to zero and let the validity checks below ensure that the arg_id + // wasn't something like "00", or "023". + if (_Ch != '0') { + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Index); + } else { + ++_Begin; + } + // The format string shouldn't end right after the index number. + // The only things permitted after the index are the end of the replacement field ('}') + // or the beginning of the format spec (':'). + if (_Begin == _End || (*_Begin != '}' && *_Begin != ':')) { + throw format_error("Invalid format string."); + } + _Callbacks._On_manual_id(_Index); + return _Begin; + } + // This is where we would parse named arg ids if std::format were to support them. + throw format_error("Invalid format string."); +} + +template _Callbacks_type> +constexpr const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + // done with parsing, or reached the end of a replacement field. + if (_Begin == _End || *_Begin == '}') { + return _Begin; + } + + // align and fill + auto _Parsed_align = _Align::_None; + auto _Align_pt = _Begin + 1; + if (_Align_pt == _End) { + _Align_pt = _Begin; + } + for (;;) { + switch (*_Align_pt) { + case '<': + _Parsed_align = _Align::_Left; + break; + case '>': + _Parsed_align = _Align::_Right; + break; + case '^': + _Parsed_align = _Align::_Center; + break; + } + if (_Parsed_align != _Align::_None) { + if (_Align_pt != _Begin) { + if (*_Begin == '{') { + throw format_error("invalid fill character '{'"); + } + _Callbacks._On_fill({_Begin, static_cast(_Align_pt - _Begin)}); + _Begin = _Align_pt + 1; + } else { + ++_Begin; + } + _Callbacks._On_align(_Parsed_align); + break; + } else if (_Align_pt == _Begin) { + break; + } + _Align_pt = _Begin; + } + return _Begin; +} + +// Adapts a type modeling _Parse_spec_callbacks to model _Parse_arg_id_callbacks. +// Used in _Parse_width so that _Parse_arg_id can be used to parse dynamic widths. +template _Callbacks_type> +struct _Width_adapter { + _Callbacks_type& _Callbacks; + + explicit constexpr _Width_adapter(_Callbacks_type& _Handler) : _Callbacks(_Handler) {} + + constexpr void _On_auto_id() { + _Callbacks._On_dynamic_width(_Auto_id_tag{}); + } + constexpr void _On_manual_id(int _Id) { + _Callbacks._On_dynamic_width(_Id); + } +}; + +template _Callbacks_type> +constexpr const _CharT* _Parse_width(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + _STL_INTERNAL_CHECK(_Begin != _End); + if ('1' <= *_Begin && *_Begin <= '9') { + int _Value = 0; + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Value); + _Callbacks._On_width(_Value); + } else if (*_Begin == '{') { + ++_Begin; + if (_Begin != _End) { + _Begin = _Parse_arg_id(_Begin, _End, _Width_adapter<_CharT, _Callbacks_type>(_Callbacks)); + } + if (_Begin == _End || *_Begin != '}') { + throw format_error("Invalid format string."); + } + ++_Begin; + } + return _Begin; +} + +template +struct formatter; + +// TODO: test coverage +template +class basic_format_parse_context { +public: + using char_type = _CharT; + using const_iterator = typename basic_string_view<_CharT>::const_iterator; + using iterator = const_iterator; + +private: + basic_string_view<_CharT> _Format_string; + size_t _Num_args; + // The standard says this is size_t, however we use ptrdiff_t to save some space + // by not having to store the indexing mode. Below is a more detailed explanation + // of how this works. + ptrdiff_t _Next_arg_id = 0; + +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; + + _NODISCARD constexpr const_iterator begin() const noexcept { + return _Format_string.begin(); + } + _NODISCARD constexpr const_iterator end() const noexcept { + return _Format_string.end(); + } + constexpr void advance_to(const_iterator _It) { + _Format_string.remove_prefix(_It - begin()); + } + + // While the standard presents an exposition only enum value for + // the indexing mode (manual, automatic, or unknown) we use _Next_arg_id to indicate it. + // _Next_arg_id == 0 means unknown + // _Next_arg_id > 0 means automatic + // _Next_arg_id == -1 means manual + constexpr size_t next_arg_id() { + if (_Next_arg_id >= 0) { + return _Next_arg_id++; + } + throw format_error("Can not switch from manual to automatic indexing"); + } + constexpr void check_arg_id(size_t _Id) { + (void) _Id; + if (_Next_arg_id > 0) { + throw format_error("Can not switch from automatic to manual indexing"); + } + _Next_arg_id = -1; + } +}; + +// TODO: test coverage +template +class basic_format_arg { +public: + class handle; + +private: + using _Char_type = typename _Context::char_type; + + using _Format_arg_value = variant, handle>; + + _Format_arg_value _Value; + +public: + class handle { + private: + const void* _Ptr; + void (*_Format)(basic_format_parse_context<_Char_type>& _Parse_ctx, _Context _Format_ctx, const void*); + friend basic_format_arg; + + public: + void format(basic_format_parse_context<_Char_type>& _Parse_ctx, _Context& _Format_ctx) { + _Format(_Parse_ctx, _Format_ctx, _Ptr); + } + }; + + basic_format_arg() noexcept = default; + explicit operator bool() const noexcept { + return !_STD holds_alternative(_Value); + } +}; + +// TODO: test coverage +template +class _Format_arg_store { + static constexpr size_t _Num_args = sizeof...(_Args); + // TODO: no arg packing yet + + using _Value_type = basic_format_arg<_Context>; +}; + +// TODO: test coverage +template +class basic_format_args { +public: + basic_format_args() noexcept; + template + basic_format_args(const _Format_arg_store<_Context, _Args...>& store) noexcept; + + basic_format_arg<_Context> get(size_t _Index) const noexcept; +}; + +// TODO: test coverage +// clang-format off +template + requires output_iterator<_Out, _CharT> +class basic_format_context { + // clang-format on +private: + _Out _OutputIt; + basic_format_args _Args; + locale _Loc; + +public: + using iterator = _Out; + using char_type = _CharT; + + template + using formatter_type = formatter<_Ty, _CharT>; + + basic_format_arg arg(size_t _Id) const { + return _Args.get(_Id); + } + locale locale() { + return _Loc; + } + + iterator out() { + return _OutputIt; + } + void advance_to(iterator _It) { + // TODO: IDL support probably required + _OutputIt = _It; + } +}; + +using format_context = basic_format_context, string::value_type>; +using wformat_context = basic_format_context, wstring::value_type>; +using format_args = basic_format_args; +using wformat_args = basic_format_args; + +// FUNCTION vformat +string vformat(string_view _Fmt, format_args _Args); +wstring vformat(wstring_view _Fmt, wformat_args _Args); +string vformat(const locale& _Loc, string_view _Fmt, format_args _Args); +wstring vformat(const locale& _Loc, wstring_view _Fmt, wformat_args _Args); + +_STD_END + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) + +#endif // __cpp_lib_concepts +#endif // _STL_COMPILER_PREPROCESSOR +#endif // _FORMAT_ diff --git a/tests/std/test.lst b/tests/std/test.lst index 2585db8b27f..c0e665ec981 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -235,6 +235,8 @@ tests\P0595R2_is_constant_evaluated tests\P0607R0_inline_variables tests\P0616R0_using_move_in_numeric tests\P0631R8_numbers_math_constants +tests\P0645R10_text_formatting_parse_contexts +tests\P0645R10_text_formatting_parsing tests\P0674R1_make_shared_for_arrays tests\P0718R2_atomic_smart_ptrs tests\P0758R1_is_nothrow_convertible diff --git a/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst b/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp b/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp new file mode 100644 index 00000000000..7a3f5c0dad2 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +// TODO: fill in tests + +int main() { + return 0; +} diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/env.lst b/tests/std/tests/P0645R10_text_formatting_parsing/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_parsing/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp new file mode 100644 index 00000000000..89aec01765a --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +using namespace std; + +// copied from the string_view tests +template +struct choose_literal; // not defined + +template <> +struct choose_literal { + static constexpr const char* choose(const char* s, const wchar_t*) { + return s; + } +}; + +template <> +struct choose_literal { + static constexpr const wchar_t* choose(const char*, const wchar_t* s) { + return s; + } +}; + +#define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) + +template +struct testing_callbacks { + _Align expected_alignment = _Align::_None; + basic_string_view expected_fill; + int expected_width = -1; + int expected_dynamic_width = -1; + bool expected_auto_dynamic_width = false; + constexpr void _On_align(_Align aln) { + assert(aln == expected_alignment); + } + constexpr void _On_fill(basic_string_view str_view) { + assert(str_view == expected_fill); + } + constexpr void _On_width(int width) { + assert(width == expected_width); + } + constexpr void _On_dynamic_width(int id) { + assert(id == expected_dynamic_width); + } + constexpr void _On_dynamic_width(_Auto_id_tag) { + assert(expected_auto_dynamic_width); + } +}; +template +testing_callbacks(_Align, basic_string_view) -> testing_callbacks; + +struct testing_arg_id_callbacks { + constexpr void _On_auto_id() {} + constexpr void _On_manual_id(int) {} +}; + +template +constexpr 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 { + auto end = func(view.data(), view.data() + view.size(), move(callbacks)); + if (expected_end_position != basic_string_view::npos) { + assert(end == view.data() + expected_end_position); + } + assert(!err_expected); + } catch (const format_error&) { + assert(err_expected); + } +} + +template +constexpr bool test_parse_align() { + auto parse_align_fn = _Parse_align>; + using view_typ = basic_string_view; + + auto s0 = view_typ(TYPED_LITERAL(CharT, "")); + auto s1 = view_typ(TYPED_LITERAL(CharT, "*<")); + auto s2 = view_typ(TYPED_LITERAL(CharT, "*>")); + auto s3 = view_typ(TYPED_LITERAL(CharT, "*^")); + + test_parse_helper(parse_align_fn, s0, false, view_typ::npos, {_Align::_None, view_typ(TYPED_LITERAL(CharT, ""))}); + test_parse_helper(parse_align_fn, s1, false, view_typ::npos, {_Align::_Left, view_typ(TYPED_LITERAL(CharT, "*"))}); + test_parse_helper(parse_align_fn, s2, false, view_typ::npos, {_Align::_Right, view_typ(TYPED_LITERAL(CharT, "*"))}); + test_parse_helper( + parse_align_fn, s3, false, view_typ::npos, {_Align::_Center, 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 + // \x343E (which is from CJK unified ideographs extension A) and similar characters to parse as + // an alignment specifier. + auto s4 = L"*\x343E"sv; + test_parse_helper(parse_align_fn, s4, false, view_typ::npos, {_Align::_None, L"*"sv}); + } + + return true; +} + +template +constexpr bool test_parse_width() { + auto parse_width_fn = _Parse_width>; + using view_typ = basic_string_view; + + auto s0 = view_typ(TYPED_LITERAL(CharT, "1")); + auto s1 = view_typ(TYPED_LITERAL(CharT, "{1}")); + auto s2 = view_typ(TYPED_LITERAL(CharT, "{0}")); + auto s3 = view_typ(TYPED_LITERAL(CharT, "{}")); + auto i0 = view_typ(TYPED_LITERAL(CharT, "0")); + auto i1 = view_typ(TYPED_LITERAL(CharT, "01")); + + test_parse_helper(parse_width_fn, s0, false, view_typ::npos, {.expected_width = 1}); + test_parse_helper(parse_width_fn, s1, false, view_typ::npos, {.expected_dynamic_width = 1}); + test_parse_helper(parse_width_fn, s2, false, view_typ::npos, {.expected_dynamic_width = 0}); + test_parse_helper(parse_width_fn, s3, false, view_typ::npos, {.expected_auto_dynamic_width = true}); + test_parse_helper(parse_width_fn, i0, false, false); + test_parse_helper(parse_width_fn, i1, false, false); + return true; +} + +template +constexpr 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 + // format spec + auto s0 = view_typ(TYPED_LITERAL(CharT, "}")); + auto s1 = view_typ(TYPED_LITERAL(CharT, ":")); + auto s2 = view_typ(TYPED_LITERAL(CharT, ":}")); + auto s3 = view_typ(TYPED_LITERAL(CharT, "0:}")); + auto s4 = view_typ(TYPED_LITERAL(CharT, "0:")); + auto s5 = view_typ(TYPED_LITERAL(CharT, "1}")); + auto i0 = view_typ(TYPED_LITERAL(CharT, "01}")); + auto i1 = view_typ(TYPED_LITERAL(CharT, "0")); + + test_parse_helper(parse_arg_id_fn, s0, false, 0); + test_parse_helper(parse_arg_id_fn, s1, false, 0); + test_parse_helper(parse_arg_id_fn, s2, false, 0); + test_parse_helper(parse_arg_id_fn, s3, false, 1); + test_parse_helper(parse_arg_id_fn, s4, false, 1); + test_parse_helper(parse_arg_id_fn, s5, false, 1); + + // can't test the expected exceptions in a constexpr + // context + if (!is_constant_evaluated()) { + test_parse_helper(parse_arg_id_fn, i0, true); + test_parse_helper(parse_arg_id_fn, i1, true); + } + + return true; +} + +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()); + return 0; +} diff --git a/tests/std/tests/include_each_header_alone_matrix.lst b/tests/std/tests/include_each_header_alone_matrix.lst index 71cb23f8d3d..6ca12287dac 100644 --- a/tests/std/tests/include_each_header_alone_matrix.lst +++ b/tests/std/tests/include_each_header_alone_matrix.lst @@ -24,6 +24,7 @@ PM_CL="/DMEOW_HEADER=deque" PM_CL="/DMEOW_HEADER=exception" PM_CL="/DMEOW_HEADER=execution" PM_CL="/DMEOW_HEADER=filesystem" +PM_CL="/DMEOW_HEADER=format" PM_CL="/DMEOW_HEADER=forward_list" PM_CL="/DMEOW_HEADER=fstream" PM_CL="/DMEOW_HEADER=functional" From 0668a74abb081d4fff1e1804514318674c8cd2ce Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Fri, 16 Oct 2020 13:35:48 -0700 Subject: [PATCH 02/94] [feature/format] add parse_precision. (#1333) * add parse_precision. * adopt style review comments. * add more tests for exceptions. * test constexpr precision too * address some more review comments Co-authored-by: Charles Barto --- stl/inc/format | 53 ++++++++++- .../P0645R10_text_formatting_parsing/test.cpp | 90 ++++++++++++++----- 2 files changed, 120 insertions(+), 23 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index addbe13cdf7..a94aa8f8496 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -46,6 +46,11 @@ concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, { _At._On_width(_STD declval()) } -> same_as; { _At._On_dynamic_width(_STD declval()) } -> same_as; { _At._On_dynamic_width(_STD declval<_Auto_id_tag>()) } -> same_as; + { _At._On_precision(_STD declval()) } -> same_as; + { _At._On_dynamic_precision(_STD declval()) } -> same_as; + { _At._On_dynamic_precision(_STD declval<_Auto_id_tag>()) } -> same_as; + + /* { _At._On_type(_STD declval<_CharT>()) } -> same_as; */ }; template concept _Parse_arg_id_callbacks = requires(_Ty _At) { @@ -86,6 +91,7 @@ constexpr const _CharT* _Parse_arg_id(const _CharT* _Begin, const _CharT* _End, _Callbacks._On_auto_id(); return _Begin; } + if (_Ch >= '0' && _Ch <= '9') { int _Index = 0; // arg_id is not allowed to have any leading zeros, but is allowed to be @@ -161,7 +167,7 @@ template _Callbacks_type> struct _Width_adapter { _Callbacks_type& _Callbacks; - explicit constexpr _Width_adapter(_Callbacks_type& _Handler) : _Callbacks(_Handler) {} + constexpr explicit _Width_adapter(_Callbacks_type& _Handler) : _Callbacks(_Handler) {} constexpr void _On_auto_id() { _Callbacks._On_dynamic_width(_Auto_id_tag{}); @@ -171,6 +177,22 @@ struct _Width_adapter { } }; +// Adapts a type modeling _Parse_spec_callbacks to model _Parse_arg_id_callbacks. +// Used in _Parse_precision so that _Parse_arg_id can be used to parse dynamic precisions. +template _Callbacks_type> +struct _Precision_adapter { + _Callbacks_type& _Callbacks; + + constexpr explicit _Precision_adapter(_Callbacks_type& _Handler) : _Callbacks(_Handler) {} + + constexpr void _On_auto_id() { + _Callbacks._On_dynamic_precision(_Auto_id_tag{}); + } + constexpr void _On_manual_id(int _Id) { + _Callbacks._On_dynamic_precision(_Id); + } +}; + template _Callbacks_type> constexpr const _CharT* _Parse_width(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { _STL_INTERNAL_CHECK(_Begin != _End); @@ -181,7 +203,7 @@ constexpr const _CharT* _Parse_width(const _CharT* _Begin, const _CharT* _End, _ } else if (*_Begin == '{') { ++_Begin; if (_Begin != _End) { - _Begin = _Parse_arg_id(_Begin, _End, _Width_adapter<_CharT, _Callbacks_type>(_Callbacks)); + _Begin = _Parse_arg_id(_Begin, _End, _Width_adapter<_CharT, _Callbacks_type>{_Callbacks}); } if (_Begin == _End || *_Begin != '}') { throw format_error("Invalid format string."); @@ -191,6 +213,33 @@ constexpr const _CharT* _Parse_width(const _CharT* _Begin, const _CharT* _End, _ return _Begin; } +template _Callbacks_type> +constexpr const _CharT* _Parse_precision(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + ++_Begin; + _CharT _Ch = '\0'; + if (_Begin != _End) { + _Ch = *_Begin; + } + + if ('0' <= _Ch && _Ch <= '9') { + int _Precision = 0; + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Precision); + _Callbacks._On_precision(_Precision); + } else if (_Ch == '{') { + ++_Begin; + if (_Begin != _End) { + _Begin = _Parse_arg_id(_Begin, _End, _Precision_adapter<_CharT, _Callbacks_type>{_Callbacks}); + } + + if (_Begin == _End || *_Begin != '}') { + throw format_error("Invalid format string."); + } + } else { + throw format_error("Missing precision specifier."); + } + return _Begin; +} + template struct formatter; diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index 89aec01765a..187dca249b5 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -33,9 +33,12 @@ template struct testing_callbacks { _Align expected_alignment = _Align::_None; basic_string_view expected_fill; - int expected_width = -1; - int expected_dynamic_width = -1; - bool expected_auto_dynamic_width = false; + int expected_width = -1; + int expected_dynamic_width = -1; + bool expected_auto_dynamic_width = false; + int expected_precision = -1; + int expected_dynamic_precision = -1; + bool expected_auto_dynamic_precision = false; constexpr void _On_align(_Align aln) { assert(aln == expected_alignment); } @@ -51,6 +54,15 @@ struct testing_callbacks { constexpr void _On_dynamic_width(_Auto_id_tag) { assert(expected_auto_dynamic_width); } + constexpr void _On_precision(int pre) { + assert(pre == expected_precision); + } + constexpr void _On_dynamic_precision(int id) { + assert(id == expected_dynamic_precision); + } + constexpr void _On_dynamic_precision(_Auto_id_tag) { + assert(expected_auto_dynamic_precision); + } }; template testing_callbacks(_Align, basic_string_view) -> testing_callbacks; @@ -81,10 +93,10 @@ constexpr bool test_parse_align() { auto parse_align_fn = _Parse_align>; using view_typ = basic_string_view; - auto s0 = view_typ(TYPED_LITERAL(CharT, "")); - auto s1 = view_typ(TYPED_LITERAL(CharT, "*<")); - auto s2 = view_typ(TYPED_LITERAL(CharT, "*>")); - auto s3 = view_typ(TYPED_LITERAL(CharT, "*^")); + view_typ s0(TYPED_LITERAL(CharT, "")); + view_typ s1(TYPED_LITERAL(CharT, "*<")); + view_typ s2(TYPED_LITERAL(CharT, "*>")); + view_typ s3(TYPED_LITERAL(CharT, "*^")); test_parse_helper(parse_align_fn, s0, false, view_typ::npos, {_Align::_None, view_typ(TYPED_LITERAL(CharT, ""))}); test_parse_helper(parse_align_fn, s1, false, view_typ::npos, {_Align::_Left, view_typ(TYPED_LITERAL(CharT, "*"))}); @@ -108,12 +120,12 @@ constexpr bool test_parse_width() { auto parse_width_fn = _Parse_width>; using view_typ = basic_string_view; - auto s0 = view_typ(TYPED_LITERAL(CharT, "1")); - auto s1 = view_typ(TYPED_LITERAL(CharT, "{1}")); - auto s2 = view_typ(TYPED_LITERAL(CharT, "{0}")); - auto s3 = view_typ(TYPED_LITERAL(CharT, "{}")); - auto i0 = view_typ(TYPED_LITERAL(CharT, "0")); - auto i1 = view_typ(TYPED_LITERAL(CharT, "01")); + view_typ s0(TYPED_LITERAL(CharT, "1")); + view_typ s1(TYPED_LITERAL(CharT, "{1}")); + view_typ s2(TYPED_LITERAL(CharT, "{0}")); + view_typ s3(TYPED_LITERAL(CharT, "{}")); + view_typ i0(TYPED_LITERAL(CharT, "0")); + view_typ i1(TYPED_LITERAL(CharT, "01")); test_parse_helper(parse_width_fn, s0, false, view_typ::npos, {.expected_width = 1}); test_parse_helper(parse_width_fn, s1, false, view_typ::npos, {.expected_dynamic_width = 1}); @@ -130,14 +142,14 @@ constexpr bool test_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 // format spec - auto s0 = view_typ(TYPED_LITERAL(CharT, "}")); - auto s1 = view_typ(TYPED_LITERAL(CharT, ":")); - auto s2 = view_typ(TYPED_LITERAL(CharT, ":}")); - auto s3 = view_typ(TYPED_LITERAL(CharT, "0:}")); - auto s4 = view_typ(TYPED_LITERAL(CharT, "0:")); - auto s5 = view_typ(TYPED_LITERAL(CharT, "1}")); - auto i0 = view_typ(TYPED_LITERAL(CharT, "01}")); - auto i1 = view_typ(TYPED_LITERAL(CharT, "0")); + view_typ s0(TYPED_LITERAL(CharT, "}")); + view_typ s1(TYPED_LITERAL(CharT, ":")); + view_typ s2(TYPED_LITERAL(CharT, ":}")); + view_typ s3(TYPED_LITERAL(CharT, "0:}")); + view_typ s4(TYPED_LITERAL(CharT, "0:")); + view_typ s5(TYPED_LITERAL(CharT, "1}")); + view_typ i0(TYPED_LITERAL(CharT, "01}")); + view_typ i1(TYPED_LITERAL(CharT, "0")); test_parse_helper(parse_arg_id_fn, s0, false, 0); test_parse_helper(parse_arg_id_fn, s1, false, 0); @@ -156,6 +168,38 @@ constexpr bool test_parse_arg_id() { return true; } +template +constexpr bool test_parse_precision() { + auto parse_pre_fn = _Parse_precision>; + using view_typ = basic_string_view; + + view_typ s0(TYPED_LITERAL(CharT, ".0")); + view_typ s1(TYPED_LITERAL(CharT, ".1")); + view_typ s2(TYPED_LITERAL(CharT, ".12")); + view_typ s3(TYPED_LITERAL(CharT, ".{1}")); + view_typ s4(TYPED_LITERAL(CharT, ".{}")); + + view_typ i0(TYPED_LITERAL(CharT, ".{ }")); + view_typ i1(TYPED_LITERAL(CharT, ".")); + view_typ i2(TYPED_LITERAL(CharT, ".{ |")); + view_typ i3(TYPED_LITERAL(CharT, ".{")); + + test_parse_helper(parse_pre_fn, s0, false, view_typ::npos, {.expected_precision = 0}); + test_parse_helper(parse_pre_fn, s1, false, view_typ::npos, {.expected_precision = 1}); + test_parse_helper(parse_pre_fn, s2, false, view_typ::npos, {.expected_precision = 12}); + test_parse_helper(parse_pre_fn, s3, false, view_typ::npos, {.expected_dynamic_precision = 1}); + test_parse_helper(parse_pre_fn, s4, false, view_typ::npos, {.expected_auto_dynamic_precision = true}); + + if (!is_constant_evaluated()) { + test_parse_helper(parse_pre_fn, i0, true); + test_parse_helper(parse_pre_fn, i1, true); + test_parse_helper(parse_pre_fn, i2, true); + test_parse_helper(parse_pre_fn, i3, true); + } + + return true; +} + int main() { test_parse_align(); test_parse_align(); @@ -169,5 +213,9 @@ int main() { 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()); return 0; } From a5fd69bfd36f75f8aa334ff53a0550725c685de2 Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Thu, 12 Nov 2020 14:02:31 -0800 Subject: [PATCH 03/94] [feature/format] parse format specs (#1378) * add parse_precision. * adopt style review comments. * add parse format spec * add tests for parse_format_specs * actually call the test functions. * fix tests. * test the return value of _Parse_format_specs * added more tests for parse_format_specs. * address review comments. * make "we're not done yet" a precondition for _Parse_align. * remove test that voilates new preconditions on _Parse_align. Co-authored-by: Charles Barto --- stl/inc/format | 78 +++++++++++++++-- .../P0645R10_text_formatting_parsing/test.cpp | 85 +++++++++++++++++-- 2 files changed, 148 insertions(+), 15 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index a94aa8f8496..16466b6980d 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -36,11 +36,13 @@ class format_error : public runtime_error { enum class _Align { _None, _Left, _Right, _Center }; +enum class _Sign { _None, _Plus, _Minus, _Space }; + struct _Auto_id_tag {}; // clang-format off template -concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln) { +concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln, _Sign _Sgn) { { _At._On_align(_Aln) } -> same_as; { _At._On_fill(_Sv) } -> same_as; { _At._On_width(_STD declval()) } -> same_as; @@ -49,8 +51,10 @@ concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, { _At._On_precision(_STD declval()) } -> same_as; { _At._On_dynamic_precision(_STD declval()) } -> same_as; { _At._On_dynamic_precision(_STD declval<_Auto_id_tag>()) } -> same_as; - - /* { _At._On_type(_STD declval<_CharT>()) } -> same_as; */ + { _At._On_sign(_Sgn) } -> same_as; + { _At._On_hash() } -> same_as; + { _At._On_zero() } -> same_as; + { _At._On_type(_STD declval<_CharT>()) } -> same_as; }; template concept _Parse_arg_id_callbacks = requires(_Ty _At) { @@ -118,11 +122,7 @@ constexpr const _CharT* _Parse_arg_id(const _CharT* _Begin, const _CharT* _End, template _Callbacks_type> constexpr const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { - // done with parsing, or reached the end of a replacement field. - if (_Begin == _End || *_Begin == '}') { - return _Begin; - } - + _STL_INTERNAL_CHECK(_Begin != _End && *_Begin != '}'); // align and fill auto _Parsed_align = _Align::_None; auto _Align_pt = _Begin + 1; @@ -240,6 +240,68 @@ constexpr const _CharT* _Parse_precision(const _CharT* _Begin, const _CharT* _En return _Begin; } +template _Callbacks_type> +constexpr const _CharT* _Parse_format_specs(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + if (_Begin == _End || *_Begin == '}') { + return _Begin; + } + + _Begin = _Parse_align(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } + + switch (*_Begin) { + case '+': + _Callbacks._On_sign(_Sign::_Plus); + ++_Begin; + break; + case '-': + _Callbacks._On_sign(_Sign::_Minus); + ++_Begin; + break; + case ' ': + _Callbacks._On_sign(_Sign::_Space); + ++_Begin; + break; + default: + break; + } + + if (_Begin == _End) { + return _Begin; + } + + if (*_Begin == '#') { + _Callbacks._On_hash(); + if (++_Begin == _End) { + return _Begin; + } + } + + if (*_Begin == '0') { + _Callbacks._On_zero(); + if (++_Begin == _End) { + return _Begin; + } + } + + _Begin = _Parse_width(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } + + if (*_Begin == '.') { + _Begin = _Parse_precision(_Begin, _End, _Callbacks); + } + + // If there's anything remaining we assume it's a type. + if (_Begin != _End && *_Begin != '}') { + _Callbacks._On_type(*_Begin++); + } + return _Begin; +} + template struct formatter; diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index 187dca249b5..85da914a9ac 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -32,6 +33,7 @@ struct choose_literal { template struct testing_callbacks { _Align expected_alignment = _Align::_None; + _Sign expected_sign = _Sign::_None; basic_string_view expected_fill; int expected_width = -1; int expected_dynamic_width = -1; @@ -39,6 +41,9 @@ struct testing_callbacks { int expected_precision = -1; int expected_dynamic_precision = -1; bool expected_auto_dynamic_precision = false; + bool expected_hash = false; + bool expected_zero = false; + CharT expected_type = '\0'; constexpr void _On_align(_Align aln) { assert(aln == expected_alignment); } @@ -63,6 +68,18 @@ struct testing_callbacks { constexpr void _On_dynamic_precision(_Auto_id_tag) { assert(expected_auto_dynamic_precision); } + constexpr void _On_sign(_Sign sgn) { + assert(sgn == expected_sign); + } + constexpr void _On_hash() { + assert(expected_hash); + } + constexpr void _On_zero() { + assert(expected_zero); + } + constexpr void _On_type(CharT type) { + assert(type == expected_type); + } }; template testing_callbacks(_Align, basic_string_view) -> testing_callbacks; @@ -93,23 +110,23 @@ constexpr bool test_parse_align() { auto parse_align_fn = _Parse_align>; using view_typ = basic_string_view; - view_typ s0(TYPED_LITERAL(CharT, "")); view_typ s1(TYPED_LITERAL(CharT, "*<")); view_typ s2(TYPED_LITERAL(CharT, "*>")); view_typ s3(TYPED_LITERAL(CharT, "*^")); - test_parse_helper(parse_align_fn, s0, false, view_typ::npos, {_Align::_None, view_typ(TYPED_LITERAL(CharT, ""))}); - test_parse_helper(parse_align_fn, s1, false, view_typ::npos, {_Align::_Left, view_typ(TYPED_LITERAL(CharT, "*"))}); - test_parse_helper(parse_align_fn, s2, false, view_typ::npos, {_Align::_Right, view_typ(TYPED_LITERAL(CharT, "*"))}); - test_parse_helper( - parse_align_fn, s3, false, view_typ::npos, {_Align::_Center, view_typ(TYPED_LITERAL(CharT, "*"))}); + test_parse_helper(parse_align_fn, s1, false, view_typ::npos, + {.expected_alignment = _Align::_Left, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*"))}); + test_parse_helper(parse_align_fn, s2, false, view_typ::npos, + {.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 // \x343E (which is from CJK unified ideographs extension A) and similar characters to parse as // an alignment specifier. auto s4 = L"*\x343E"sv; - test_parse_helper(parse_align_fn, s4, false, view_typ::npos, {_Align::_None, L"*"sv}); + test_parse_helper(parse_align_fn, s4, false, view_typ::npos, {.expected_fill = L"*"sv}); } return true; @@ -200,22 +217,76 @@ constexpr bool test_parse_precision() { return true; } +template +constexpr bool test_parse_format_specs() { + auto parse_format_specs_fn = _Parse_format_specs>; + using view_typ = basic_string_view; + + view_typ s0(TYPED_LITERAL(CharT, "6}")); + view_typ s1(TYPED_LITERAL(CharT, "*<6")); + view_typ s2(TYPED_LITERAL(CharT, "*>6}")); + view_typ s3(TYPED_LITERAL(CharT, "*^6}")); + view_typ s4(TYPED_LITERAL(CharT, "6d}")); + view_typ s5(TYPED_LITERAL(CharT, "*^+4.4a}")); + view_typ s6(TYPED_LITERAL(CharT, "*^+#04.4a}")); + test_parse_helper(parse_format_specs_fn, s0, false, s0.size() - 1, {.expected_width = 6}); + test_parse_helper(parse_format_specs_fn, s1, false, s1.size(), + {.expected_alignment = _Align::_Left, + .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), + .expected_width = 6}); + test_parse_helper(parse_format_specs_fn, s2, false, s2.size() - 1, + {.expected_alignment = _Align::_Right, + .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), + .expected_width = 6}); + test_parse_helper(parse_format_specs_fn, s3, false, s3.size() - 1, + {.expected_alignment = _Align::_Center, + .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), + .expected_width = 6}); + test_parse_helper(parse_format_specs_fn, s4, false, s4.size() - 1, {.expected_width = 6, .expected_type = 'd'}); + test_parse_helper(parse_format_specs_fn, s5, false, s5.size() - 1, + {.expected_alignment = _Align::_Center, + .expected_sign = _Sign::_Plus, + .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), + .expected_width = 4, + .expected_precision = 4, + .expected_type = 'a'}); + test_parse_helper(parse_format_specs_fn, s6, false, s6.size() - 1, + {.expected_alignment = _Align::_Center, + .expected_sign = _Sign::_Plus, + .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), + .expected_width = 4, + .expected_precision = 4, + .expected_hash = true, + .expected_zero = true, + .expected_type = 'a'}); + return true; +} + 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()); + return 0; } From 3460df58c3aa8a77266f79cc54dc713469413f75 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 2 Feb 2021 19:04:00 -0800 Subject: [PATCH 04/94] Unskip format.error/format.error.pass.cpp. --- tests/libcxx/expected_results.txt | 1 - tests/libcxx/skipped_tests.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index ed1d3397670..5be018f1745 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -307,7 +307,6 @@ std/utilities/variant/variant.variant/variant.ctor/T.pass.cpp FAIL # C++20 P0645R10 " Text Formatting" std/language.support/support.limits/support.limits.general/format.version.pass.cpp FAIL -std/utilities/format/format.error/format.error.pass.cpp FAIL # C++20 P0784R7 "More constexpr containers" std/utilities/memory/allocator.traits/allocator.traits.members/allocate.pass.cpp:0 FAIL diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index d50700aaccf..b3663f07368 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -307,7 +307,6 @@ utilities\variant\variant.variant\variant.ctor\T.pass.cpp # C++20 P0645R10 " Text Formatting" language.support\support.limits\support.limits.general\format.version.pass.cpp -utilities\format\format.error\format.error.pass.cpp # C++20 P0784R7 "More constexpr containers" utilities\memory\allocator.traits\allocator.traits.members\allocate.pass.cpp From dfb9396a8228fab636e3b4da5c3d456826358d69 Mon Sep 17 00:00:00 2001 From: MattStephanson <68978048+MattStephanson@users.noreply.github.com> Date: Thu, 4 Feb 2021 14:21:30 -0800 Subject: [PATCH 05/94] charconv internal functions for wchar_t buffers (#1587) * charconv internal functions for wchar_t buffers * fill_n for zero filling * code review feedback - actually call wide Ryu functions - Widen __[df]2s_buffered_n - Don't wrap memcpy calls. - Use ternary operator and cast for static widen. - Restore _CSTD memcpy. * Test digit pairs with a static table. Co-authored-by: Stephan T. Lavavej --- stl/inc/xcharconv_ryu.h | 252 ++++++++++-------- stl/inc/xcharconv_ryu_tables.h | 17 +- tests/std/tests/P0067R5_charconv/test.cpp | 38 +++ tests/std/tests/P0067R5_charconv/test.hpp | 19 ++ .../P0067R5_charconv/wchar_test_cases.hpp | 167 ++++++++++++ 5 files changed, 380 insertions(+), 113 deletions(-) create mode 100644 tests/std/tests/P0067R5_charconv/wchar_test_cases.hpp diff --git a/stl/inc/xcharconv_ryu.h b/stl/inc/xcharconv_ryu.h index 1b2b95a8e05..e76714cbcd7 100644 --- a/stl/inc/xcharconv_ryu.h +++ b/stl/inc/xcharconv_ryu.h @@ -39,8 +39,11 @@ #if _STL_COMPILER_PREPROCESSOR #include +#include +#include #include #include +#include #ifdef _M_X64 #include // for _umul128() and __shiftright128() @@ -389,7 +392,10 @@ _NODISCARD inline uint32_t __mulShift_mod1e9(const uint64_t __m, const uint64_t* #endif // ^^^ intrinsics unavailable ^^^ } -inline void __append_n_digits(const uint32_t __olength, uint32_t __digits, char* const __result) { +#define _WIDEN(_TYPE, _CHAR) static_cast<_TYPE>(is_same_v<_TYPE, char> ? _CHAR : L##_CHAR) + +template +void __append_n_digits(const uint32_t __olength, uint32_t __digits, _CharT* const __result) { uint32_t __i = 0; while (__digits >= 10000) { #ifdef __clang__ // TRANSITION, LLVM-38217 @@ -400,21 +406,21 @@ inline void __append_n_digits(const uint32_t __olength, uint32_t __digits, char* __digits /= 10000; const uint32_t __c0 = (__c % 100) << 1; const uint32_t __c1 = (__c / 100) << 1; - _CSTD memcpy(__result + __olength - __i - 2, __DIGIT_TABLE + __c0, 2); - _CSTD memcpy(__result + __olength - __i - 4, __DIGIT_TABLE + __c1, 2); + _CSTD memcpy(__result + __olength - __i - 2, __DIGIT_TABLE<_CharT> + __c0, 2 * sizeof(_CharT)); + _CSTD memcpy(__result + __olength - __i - 4, __DIGIT_TABLE<_CharT> + __c1, 2 * sizeof(_CharT)); __i += 4; } if (__digits >= 100) { const uint32_t __c = (__digits % 100) << 1; __digits /= 100; - _CSTD memcpy(__result + __olength - __i - 2, __DIGIT_TABLE + __c, 2); + _CSTD memcpy(__result + __olength - __i - 2, __DIGIT_TABLE<_CharT> + __c, 2 * sizeof(_CharT)); __i += 2; } if (__digits >= 10) { const uint32_t __c = __digits << 1; - _CSTD memcpy(__result + __olength - __i - 2, __DIGIT_TABLE + __c, 2); + _CSTD memcpy(__result + __olength - __i - 2, __DIGIT_TABLE<_CharT> + __c, 2 * sizeof(_CharT)); } else { - __result[0] = static_cast('0' + __digits); + __result[0] = static_cast<_CharT>(_WIDEN(_CharT, '0') + __digits); } } @@ -429,43 +435,45 @@ inline void __append_d_digits(const uint32_t __olength, uint32_t __digits, char* __digits /= 10000; const uint32_t __c0 = (__c % 100) << 1; const uint32_t __c1 = (__c / 100) << 1; - _CSTD memcpy(__result + __olength + 1 - __i - 2, __DIGIT_TABLE + __c0, 2); - _CSTD memcpy(__result + __olength + 1 - __i - 4, __DIGIT_TABLE + __c1, 2); + _CSTD memcpy(__result + __olength + 1 - __i - 2, __DIGIT_TABLE + __c0, 2); + _CSTD memcpy(__result + __olength + 1 - __i - 4, __DIGIT_TABLE + __c1, 2); __i += 4; } if (__digits >= 100) { const uint32_t __c = (__digits % 100) << 1; __digits /= 100; - _CSTD memcpy(__result + __olength + 1 - __i - 2, __DIGIT_TABLE + __c, 2); + _CSTD memcpy(__result + __olength + 1 - __i - 2, __DIGIT_TABLE + __c, 2); __i += 2; } if (__digits >= 10) { const uint32_t __c = __digits << 1; - __result[2] = __DIGIT_TABLE[__c + 1]; + __result[2] = __DIGIT_TABLE[__c + 1]; __result[1] = '.'; - __result[0] = __DIGIT_TABLE[__c]; + __result[0] = __DIGIT_TABLE[__c]; } else { __result[1] = '.'; __result[0] = static_cast('0' + __digits); } } -inline void __append_c_digits(const uint32_t __count, uint32_t __digits, char* const __result) { +template +void __append_c_digits(const uint32_t __count, uint32_t __digits, _CharT* const __result) { uint32_t __i = 0; for (; __i < __count - 1; __i += 2) { const uint32_t __c = (__digits % 100) << 1; __digits /= 100; - _CSTD memcpy(__result + __count - __i - 2, __DIGIT_TABLE + __c, 2); + _CSTD memcpy(__result + __count - __i - 2, __DIGIT_TABLE<_CharT> + __c, 2 * sizeof(_CharT)); } if (__i < __count) { - const char __c = static_cast('0' + (__digits % 10)); + const _CharT __c = static_cast<_CharT>(_WIDEN(_CharT, '0') + (__digits % 10)); __result[__count - __i - 1] = __c; } } -inline void __append_nine_digits(uint32_t __digits, char* const __result) { +template +void __append_nine_digits(uint32_t __digits, _CharT* const __result) { if (__digits == 0) { - _CSTD memset(__result, '0', 9); + _STD fill_n(__result, 9, _WIDEN(_CharT, '0')); return; } @@ -478,10 +486,10 @@ inline void __append_nine_digits(uint32_t __digits, char* const __result) { __digits /= 10000; const uint32_t __c0 = (__c % 100) << 1; const uint32_t __c1 = (__c / 100) << 1; - _CSTD memcpy(__result + 7 - __i, __DIGIT_TABLE + __c0, 2); - _CSTD memcpy(__result + 5 - __i, __DIGIT_TABLE + __c1, 2); + _CSTD memcpy(__result + 7 - __i, __DIGIT_TABLE<_CharT> + __c0, 2 * sizeof(_CharT)); + _CSTD memcpy(__result + 5 - __i, __DIGIT_TABLE<_CharT> + __c1, 2 * sizeof(_CharT)); } - __result[0] = static_cast('0' + __digits); + __result[0] = static_cast<_CharT>(_WIDEN(_CharT, '0') + __digits); } _NODISCARD inline uint32_t __indexForExponent(const uint32_t __e) { @@ -497,9 +505,10 @@ _NODISCARD inline uint32_t __lengthForIndex(const uint32_t __idx) { return (__log10Pow2(16 * static_cast(__idx)) + 1 + 16 + 8) / 9; } -_NODISCARD inline to_chars_result __d2fixed_buffered_n(char* _First, char* const _Last, const double __d, +template +_NODISCARD pair<_CharT*, errc> __d2fixed_buffered_n(_CharT* _First, _CharT* const _Last, const double __d, const uint32_t __precision) { - char* const _Original_first = _First; + _CharT* const _Original_first = _First; const uint64_t __bits = __double_to_bits(__d); @@ -513,10 +522,10 @@ _NODISCARD inline to_chars_result __d2fixed_buffered_n(char* _First, char* const return { _Last, errc::value_too_large }; } - *_First++ = '0'; + *_First++ = _WIDEN(_CharT, '0'); if (__precision > 0) { - *_First++ = '.'; - _CSTD memset(_First, '0', __precision); + *_First++ = _WIDEN(_CharT, '.'); + _STD fill_n(_First, __precision, _WIDEN(_CharT, '0')); _First += __precision; } return { _First, errc{} }; @@ -568,13 +577,13 @@ _NODISCARD inline to_chars_result __d2fixed_buffered_n(char* _First, char* const if (_First == _Last) { return { _Last, errc::value_too_large }; } - *_First++ = '0'; + *_First++ = _WIDEN(_CharT, '0'); } if (__precision > 0) { if (_First == _Last) { return { _Last, errc::value_too_large }; } - *_First++ = '.'; + *_First++ = _WIDEN(_CharT, '.'); } if (__e2 < 0) { const int32_t __idx = -__e2 / 16; @@ -587,14 +596,14 @@ _NODISCARD inline to_chars_result __d2fixed_buffered_n(char* _First, char* const if (_Last - _First < static_cast(__precision)) { return { _Last, errc::value_too_large }; } - _CSTD memset(_First, '0', __precision); + _STD fill_n(_First, __precision, _WIDEN(_CharT, '0')); _First += __precision; } else if (__i < __MIN_BLOCK_2[__idx]) { __i = __MIN_BLOCK_2[__idx]; if (_Last - _First < static_cast(9 * __i)) { return { _Last, errc::value_too_large }; } - _CSTD memset(_First, '0', 9 * __i); + _STD fill_n(_First, 9 * __i, _WIDEN(_CharT, '0')); _First += 9 * __i; } for (; __i < __blocks; ++__i) { @@ -607,7 +616,7 @@ _NODISCARD inline to_chars_result __d2fixed_buffered_n(char* _First, char* const if (_Last - _First < static_cast(__fill)) { return { _Last, errc::value_too_large }; } - _CSTD memset(_First, '0', __fill); + _STD fill_n(_First, __fill, _WIDEN(_CharT, '0')); _First += __fill; break; } @@ -647,31 +656,31 @@ _NODISCARD inline to_chars_result __d2fixed_buffered_n(char* _First, char* const } } if (__roundUp != 0) { - char* _Round = _First; - char* _Dot = _Last; + _CharT* _Round = _First; + _CharT* _Dot = _Last; while (true) { if (_Round == _Original_first) { - _Round[0] = '1'; + _Round[0] = _WIDEN(_CharT, '1'); if (_Dot != _Last) { - _Dot[0] = '0'; - _Dot[1] = '.'; + _Dot[0] = _WIDEN(_CharT, '0'); + _Dot[1] = _WIDEN(_CharT, '.'); } if (_First == _Last) { return { _Last, errc::value_too_large }; } - *_First++ = '0'; + *_First++ = _WIDEN(_CharT, '0'); break; } --_Round; - const char __c = _Round[0]; + const _CharT __c = _Round[0]; if (__c == '.') { _Dot = _Round; - } else if (__c == '9') { - _Round[0] = '0'; + } else if (__c == _WIDEN(_CharT, '9')) { + _Round[0] = _WIDEN(_CharT, '0'); __roundUp = 1; } else { if (__roundUp == 1 || __c % 2 != 0) { - _Round[0] = __c + 1; + _Round[0] = static_cast<_CharT>(__c + 1); } break; } @@ -681,7 +690,7 @@ _NODISCARD inline to_chars_result __d2fixed_buffered_n(char* _First, char* const if (_Last - _First < static_cast(__precision)) { return { _Last, errc::value_too_large }; } - _CSTD memset(_First, '0', __precision); + _STD fill_n(_First, __precision, _WIDEN(_CharT, '0')); _First += __precision; } return { _First, errc{} }; @@ -919,11 +928,11 @@ _NODISCARD inline to_chars_result __d2exp_buffered_n(char* _First, char* const _ if (__exp >= 100) { const int32_t __c = __exp % 10; - _CSTD memcpy(_First, __DIGIT_TABLE + 2 * (__exp / 10), 2); + _CSTD memcpy(_First, __DIGIT_TABLE + 2 * (__exp / 10), 2); _First[2] = static_cast('0' + __c); _First += 3; } else { - _CSTD memcpy(_First, __DIGIT_TABLE + 2 * __exp, 2); + _CSTD memcpy(_First, __DIGIT_TABLE + 2 * __exp, 2); _First += 2; } @@ -1175,7 +1184,8 @@ _NODISCARD inline __floating_decimal_32 __f2d(const uint32_t __ieeeMantissa, con return __fd; } -_NODISCARD inline to_chars_result _Large_integer_to_chars(char* const _First, char* const _Last, +template +_NODISCARD pair<_CharT*, errc> _Large_integer_to_chars(_CharT* const _First, _CharT* const _Last, const uint32_t _Mantissa2, const int32_t _Exponent2) { // Print the integer _Mantissa2 * 2^_Exponent2 exactly. @@ -1283,7 +1293,7 @@ _NODISCARD inline to_chars_result _Large_integer_to_chars(char* const _First, ch return { _Last, errc::value_too_large }; } - char* _Result = _First; + _CharT* _Result = _First; // Print _Data[0]. While it's up to 10 digits, // which is more than Ryu generates, the code below can handle this. @@ -1299,7 +1309,8 @@ _NODISCARD inline to_chars_result _Large_integer_to_chars(char* const _First, ch return { _Result, errc{} }; } -_NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _Last, const __floating_decimal_32 __v, +template +_NODISCARD pair<_CharT*, errc> __to_chars(_CharT* const _First, _CharT* const _Last, const __floating_decimal_32 __v, chars_format _Fmt, const uint32_t __ieeeMantissa, const uint32_t __ieeeExponent) { // Step 5: Print the decimal representation. uint32_t __output = __v.__mantissa; @@ -1385,7 +1396,7 @@ _NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _La return { _Last, errc::value_too_large }; } - char* _Mid; + _CharT* _Mid; if (_Ryu_exponent > 0) { // case "172900" bool _Can_use_ryu; @@ -1448,35 +1459,35 @@ _NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _La __output /= 10000; const uint32_t __c0 = (__c % 100) << 1; const uint32_t __c1 = (__c / 100) << 1; - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __c0, 2); - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __c1, 2); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __c0, 2 * sizeof(_CharT)); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __c1, 2 * sizeof(_CharT)); } if (__output >= 100) { const uint32_t __c = (__output % 100) << 1; __output /= 100; - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __c, 2); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __c, 2 * sizeof(_CharT)); } if (__output >= 10) { const uint32_t __c = __output << 1; - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __c, 2); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __c, 2 * sizeof(_CharT)); } else { - *--_Mid = static_cast('0' + __output); + *--_Mid = static_cast<_CharT>(_WIDEN(_CharT, '0') + __output); } if (_Ryu_exponent > 0) { // case "172900" with _Can_use_ryu // Performance note: it might be more efficient to do this immediately after setting _Mid. - _CSTD memset(_First + __olength, '0', static_cast(_Ryu_exponent)); + _STD fill_n(_First + __olength, _Ryu_exponent, _WIDEN(_CharT, '0')); } else if (_Ryu_exponent == 0) { // case "1729" // Done! } else if (_Whole_digits > 0) { // case "17.29" // Performance note: moving digits might not be optimal. - _CSTD memmove(_First, _First + 1, static_cast(_Whole_digits)); + _CSTD memmove(_First, _First + 1, static_cast(_Whole_digits) * sizeof(_CharT)); _First[_Whole_digits] = '.'; } else { // case "0.001729" // Performance note: a larger memset() followed by overwriting '.' might be more efficient. - _First[0] = '0'; - _First[1] = '.'; - _CSTD memset(_First + 2, '0', static_cast(-_Whole_digits)); + _First[0] = _WIDEN(_CharT, '0'); + _First[1] = _WIDEN(_CharT, '.'); + _STD fill_n(_First + 2, -_Whole_digits, _WIDEN(_CharT, '0')); } return { _First + _Total_fixed_length, errc{} }; @@ -1487,7 +1498,7 @@ _NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _La if (_Last - _First < static_cast(_Total_scientific_length)) { return { _Last, errc::value_too_large }; } - char* const __result = _First; + _CharT* const __result = _First; // Print the decimal digits. uint32_t __i = 0; @@ -1500,50 +1511,55 @@ _NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _La __output /= 10000; const uint32_t __c0 = (__c % 100) << 1; const uint32_t __c1 = (__c / 100) << 1; - _CSTD memcpy(__result + __olength - __i - 1, __DIGIT_TABLE + __c0, 2); - _CSTD memcpy(__result + __olength - __i - 3, __DIGIT_TABLE + __c1, 2); + _CSTD memcpy(__result + __olength - __i - 1, __DIGIT_TABLE<_CharT> + __c0, 2 * sizeof(_CharT)); + _CSTD memcpy(__result + __olength - __i - 3, __DIGIT_TABLE<_CharT> + __c1, 2 * sizeof(_CharT)); __i += 4; } if (__output >= 100) { const uint32_t __c = (__output % 100) << 1; __output /= 100; - _CSTD memcpy(__result + __olength - __i - 1, __DIGIT_TABLE + __c, 2); + _CSTD memcpy(__result + __olength - __i - 1, __DIGIT_TABLE<_CharT> + __c, 2 * sizeof(_CharT)); __i += 2; } if (__output >= 10) { const uint32_t __c = __output << 1; // We can't use memcpy here: the decimal dot goes between these two digits. - __result[2] = __DIGIT_TABLE[__c + 1]; - __result[0] = __DIGIT_TABLE[__c]; + __result[2] = __DIGIT_TABLE<_CharT>[__c + 1]; + __result[0] = __DIGIT_TABLE<_CharT>[__c]; } else { - __result[0] = static_cast('0' + __output); + __result[0] = static_cast<_CharT>(_WIDEN(_CharT, '0') + __output); } // Print decimal point if needed. uint32_t __index; if (__olength > 1) { - __result[1] = '.'; + __result[1] = _WIDEN(_CharT, '.'); __index = __olength + 1; } else { __index = 1; } // Print the exponent. - __result[__index++] = 'e'; + __result[__index++] = _WIDEN(_CharT, 'e'); if (_Scientific_exponent < 0) { - __result[__index++] = '-'; + __result[__index++] = _WIDEN(_CharT, '-'); _Scientific_exponent = -_Scientific_exponent; } else { - __result[__index++] = '+'; + __result[__index++] = _WIDEN(_CharT, '+'); } - _CSTD memcpy(__result + __index, __DIGIT_TABLE + 2 * _Scientific_exponent, 2); + _CSTD memcpy(__result + __index, __DIGIT_TABLE<_CharT> + 2 * _Scientific_exponent, 2 * sizeof(_CharT)); __index += 2; return { _First + _Total_scientific_length, errc{} }; } -_NODISCARD inline to_chars_result __f2s_buffered_n(char* const _First, char* const _Last, const float __f, +_NODISCARD inline to_chars_result _Convert_to_chars_result(const pair& _Pair) { + return {_Pair.first, _Pair.second}; +} + +template +_NODISCARD pair<_CharT*, errc> __f2s_buffered_n(_CharT* const _First, _CharT* const _Last, const float __f, const chars_format _Fmt) { // Step 1: Decode the floating-point number, and unify normalized and subnormal cases. @@ -1556,7 +1572,11 @@ _NODISCARD inline to_chars_result __f2s_buffered_n(char* const _First, char* con return { _Last, errc::value_too_large }; } - _CSTD memcpy(_First, "0e+00", 5); + if constexpr (is_same_v<_CharT, char>) { + _CSTD memcpy(_First, "0e+00", 5); + } else { + _CSTD memcpy(_First, L"0e+00", 5 * sizeof(wchar_t)); + } return { _First + 5, errc{} }; } @@ -1566,7 +1586,7 @@ _NODISCARD inline to_chars_result __f2s_buffered_n(char* const _First, char* con return { _Last, errc::value_too_large }; } - *_First = '0'; + *_First = _WIDEN(_CharT, '0'); return { _First + 1, errc{} }; } @@ -1897,7 +1917,8 @@ _NODISCARD inline __floating_decimal_64 __d2d(const uint64_t __ieeeMantissa, con return __fd; } -_NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _Last, const __floating_decimal_64 __v, +template +_NODISCARD pair<_CharT*, errc> __to_chars(_CharT* const _First, _CharT* const _Last, const __floating_decimal_64 __v, chars_format _Fmt, const double __f) { // Step 5: Print the decimal representation. uint64_t __output = __v.__mantissa; @@ -1989,7 +2010,7 @@ _NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _La return { _Last, errc::value_too_large }; } - char* _Mid; + _CharT* _Mid; if (_Ryu_exponent > 0) { // case "172900" bool _Can_use_ryu; @@ -2072,10 +2093,10 @@ _NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _La const uint32_t __d0 = (__d % 100) << 1; const uint32_t __d1 = (__d / 100) << 1; - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __c0, 2); - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __c1, 2); - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __d0, 2); - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __d1, 2); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __c0, 2 * sizeof(_CharT)); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __c1, 2 * sizeof(_CharT)); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __d0, 2 * sizeof(_CharT)); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __d1, 2 * sizeof(_CharT)); } uint32_t __output2 = static_cast(__output); while (__output2 >= 10000) { @@ -2087,35 +2108,35 @@ _NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _La __output2 /= 10000; const uint32_t __c0 = (__c % 100) << 1; const uint32_t __c1 = (__c / 100) << 1; - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __c0, 2); - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __c1, 2); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __c0, 2 * sizeof(_CharT)); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __c1, 2 * sizeof(_CharT)); } if (__output2 >= 100) { const uint32_t __c = (__output2 % 100) << 1; __output2 /= 100; - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __c, 2); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __c, 2 * sizeof(_CharT)); } if (__output2 >= 10) { const uint32_t __c = __output2 << 1; - _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE + __c, 2); + _CSTD memcpy(_Mid -= 2, __DIGIT_TABLE<_CharT> + __c, 2 * sizeof(_CharT)); } else { - *--_Mid = static_cast('0' + __output2); + *--_Mid = static_cast<_CharT>(_WIDEN(_CharT, '0') + __output2); } if (_Ryu_exponent > 0) { // case "172900" with _Can_use_ryu // Performance note: it might be more efficient to do this immediately after setting _Mid. - _CSTD memset(_First + __olength, '0', static_cast(_Ryu_exponent)); + _STD fill_n(_First + __olength, _Ryu_exponent, _WIDEN(_CharT, '0')); } else if (_Ryu_exponent == 0) { // case "1729" // Done! } else if (_Whole_digits > 0) { // case "17.29" // Performance note: moving digits might not be optimal. - _CSTD memmove(_First, _First + 1, static_cast(_Whole_digits)); - _First[_Whole_digits] = '.'; + _CSTD memmove(_First, _First + 1, static_cast(_Whole_digits) * sizeof(_CharT)); + _First[_Whole_digits] = _WIDEN(_CharT, '.'); } else { // case "0.001729" // Performance note: a larger memset() followed by overwriting '.' might be more efficient. - _First[0] = '0'; - _First[1] = '.'; - _CSTD memset(_First + 2, '0', static_cast(-_Whole_digits)); + _First[0] = _WIDEN(_CharT, '0'); + _First[1] = _WIDEN(_CharT, '.'); + _STD fill_n(_First + 2, -_Whole_digits, _WIDEN(_CharT, '0')); } return { _First + _Total_fixed_length, errc{} }; @@ -2126,7 +2147,7 @@ _NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _La if (_Last - _First < static_cast(_Total_scientific_length)) { return { _Last, errc::value_too_large }; } - char* const __result = _First; + _CharT* const __result = _First; // Print the decimal digits. uint32_t __i = 0; @@ -2147,10 +2168,10 @@ _NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _La const uint32_t __c1 = (__c / 100) << 1; const uint32_t __d0 = (__d % 100) << 1; const uint32_t __d1 = (__d / 100) << 1; - _CSTD memcpy(__result + __olength - __i - 1, __DIGIT_TABLE + __c0, 2); - _CSTD memcpy(__result + __olength - __i - 3, __DIGIT_TABLE + __c1, 2); - _CSTD memcpy(__result + __olength - __i - 5, __DIGIT_TABLE + __d0, 2); - _CSTD memcpy(__result + __olength - __i - 7, __DIGIT_TABLE + __d1, 2); + _CSTD memcpy(__result + __olength - __i - 1, __DIGIT_TABLE<_CharT> + __c0, 2 * sizeof(_CharT)); + _CSTD memcpy(__result + __olength - __i - 3, __DIGIT_TABLE<_CharT> + __c1, 2 * sizeof(_CharT)); + _CSTD memcpy(__result + __olength - __i - 5, __DIGIT_TABLE<_CharT> + __d0, 2 * sizeof(_CharT)); + _CSTD memcpy(__result + __olength - __i - 7, __DIGIT_TABLE<_CharT> + __d1, 2 * sizeof(_CharT)); __i += 8; } uint32_t __output2 = static_cast(__output); @@ -2163,50 +2184,50 @@ _NODISCARD inline to_chars_result __to_chars(char* const _First, char* const _La __output2 /= 10000; const uint32_t __c0 = (__c % 100) << 1; const uint32_t __c1 = (__c / 100) << 1; - _CSTD memcpy(__result + __olength - __i - 1, __DIGIT_TABLE + __c0, 2); - _CSTD memcpy(__result + __olength - __i - 3, __DIGIT_TABLE + __c1, 2); + _CSTD memcpy(__result + __olength - __i - 1, __DIGIT_TABLE<_CharT> + __c0, 2 * sizeof(_CharT)); + _CSTD memcpy(__result + __olength - __i - 3, __DIGIT_TABLE<_CharT> + __c1, 2 * sizeof(_CharT)); __i += 4; } if (__output2 >= 100) { const uint32_t __c = (__output2 % 100) << 1; __output2 /= 100; - _CSTD memcpy(__result + __olength - __i - 1, __DIGIT_TABLE + __c, 2); + _CSTD memcpy(__result + __olength - __i - 1, __DIGIT_TABLE<_CharT> + __c, 2 * sizeof(_CharT)); __i += 2; } if (__output2 >= 10) { const uint32_t __c = __output2 << 1; // We can't use memcpy here: the decimal dot goes between these two digits. - __result[2] = __DIGIT_TABLE[__c + 1]; - __result[0] = __DIGIT_TABLE[__c]; + __result[2] = __DIGIT_TABLE<_CharT>[__c + 1]; + __result[0] = __DIGIT_TABLE<_CharT>[__c]; } else { - __result[0] = static_cast('0' + __output2); + __result[0] = static_cast<_CharT>(_WIDEN(_CharT, '0') + __output2); } // Print decimal point if needed. uint32_t __index; if (__olength > 1) { - __result[1] = '.'; + __result[1] = _WIDEN(_CharT, '.'); __index = __olength + 1; } else { __index = 1; } // Print the exponent. - __result[__index++] = 'e'; + __result[__index++] = _WIDEN(_CharT, 'e'); if (_Scientific_exponent < 0) { - __result[__index++] = '-'; + __result[__index++] = _WIDEN(_CharT, '-'); _Scientific_exponent = -_Scientific_exponent; } else { - __result[__index++] = '+'; + __result[__index++] = _WIDEN(_CharT, '+'); } if (_Scientific_exponent >= 100) { const int32_t __c = _Scientific_exponent % 10; - _CSTD memcpy(__result + __index, __DIGIT_TABLE + 2 * (_Scientific_exponent / 10), 2); - __result[__index + 2] = static_cast('0' + __c); + _CSTD memcpy(__result + __index, __DIGIT_TABLE<_CharT> + 2 * (_Scientific_exponent / 10), 2 * sizeof(_CharT)); + __result[__index + 2] = static_cast<_CharT>(_WIDEN(_CharT, '0') + __c); __index += 3; } else { - _CSTD memcpy(__result + __index, __DIGIT_TABLE + 2 * _Scientific_exponent, 2); + _CSTD memcpy(__result + __index, __DIGIT_TABLE<_CharT> + 2 * _Scientific_exponent, 2 * sizeof(_CharT)); __index += 2; } @@ -2245,7 +2266,8 @@ _NODISCARD inline bool __d2d_small_int(const uint64_t __ieeeMantissa, const uint return true; } -_NODISCARD inline to_chars_result __d2s_buffered_n(char* const _First, char* const _Last, const double __f, +template +_NODISCARD pair<_CharT*, errc> __d2s_buffered_n(_CharT* const _First, _CharT* const _Last, const double __f, const chars_format _Fmt) { // Step 1: Decode the floating-point number, and unify normalized and subnormal cases. @@ -2258,7 +2280,11 @@ _NODISCARD inline to_chars_result __d2s_buffered_n(char* const _First, char* con return { _Last, errc::value_too_large }; } - _CSTD memcpy(_First, "0e+00", 5); + if constexpr (is_same_v<_CharT, char>) { + _CSTD memcpy(_First, "0e+00", 5); + } else { + _CSTD memcpy(_First, L"0e+00", 5 * sizeof(wchar_t)); + } return { _First + 5, errc{} }; } @@ -2268,7 +2294,7 @@ _NODISCARD inline to_chars_result __d2s_buffered_n(char* const _First, char* con return { _Last, errc::value_too_large }; } - *_First = '0'; + *_First = _WIDEN(_CharT, '0'); return { _First + 1, errc{} }; } @@ -2331,9 +2357,9 @@ template _NODISCARD to_chars_result _Floating_to_chars_ryu( char* const _First, char* const _Last, const _Floating _Value, const chars_format _Fmt) noexcept { if constexpr (is_same_v<_Floating, float>) { - return __f2s_buffered_n(_First, _Last, _Value, _Fmt); + return _Convert_to_chars_result(__f2s_buffered_n(_First, _Last, _Value, _Fmt)); } else { - return __d2s_buffered_n(_First, _Last, _Value, _Fmt); + return _Convert_to_chars_result(__d2s_buffered_n(_First, _Last, _Value, _Fmt)); } } @@ -2376,11 +2402,13 @@ _NODISCARD to_chars_result _Floating_to_chars_fixed_precision( return {_Last, errc::value_too_large}; } - return __d2fixed_buffered_n(_First, _Last, _Value, static_cast(_Precision)); + return _Convert_to_chars_result(__d2fixed_buffered_n(_First, _Last, _Value, static_cast(_Precision))); } _STD_END +#undef _WIDEN + #pragma pop_macro("new") _STL_RESTORE_CLANG_WARNINGS #pragma warning(pop) diff --git a/stl/inc/xcharconv_ryu_tables.h b/stl/inc/xcharconv_ryu_tables.h index 508d3479fdf..87bc0c2b34c 100644 --- a/stl/inc/xcharconv_ryu_tables.h +++ b/stl/inc/xcharconv_ryu_tables.h @@ -63,7 +63,9 @@ _STD_BEGIN // A table of all two-digit numbers. This is used to speed up decimal digit // generation by copying pairs of digits into the final output. -inline constexpr char __DIGIT_TABLE[200] = { +template inline constexpr _CharT __DIGIT_TABLE[] = {_CharT{}}; + +template <> inline constexpr char __DIGIT_TABLE[200] = { '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', @@ -76,6 +78,19 @@ inline constexpr char __DIGIT_TABLE[200] = { '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' }; +template <> inline constexpr wchar_t __DIGIT_TABLE[200] = { + L'0',L'0',L'0',L'1',L'0',L'2',L'0',L'3',L'0',L'4',L'0',L'5',L'0',L'6',L'0',L'7',L'0',L'8',L'0',L'9', + L'1',L'0',L'1',L'1',L'1',L'2',L'1',L'3',L'1',L'4',L'1',L'5',L'1',L'6',L'1',L'7',L'1',L'8',L'1',L'9', + L'2',L'0',L'2',L'1',L'2',L'2',L'2',L'3',L'2',L'4',L'2',L'5',L'2',L'6',L'2',L'7',L'2',L'8',L'2',L'9', + L'3',L'0',L'3',L'1',L'3',L'2',L'3',L'3',L'3',L'4',L'3',L'5',L'3',L'6',L'3',L'7',L'3',L'8',L'3',L'9', + L'4',L'0',L'4',L'1',L'4',L'2',L'4',L'3',L'4',L'4',L'4',L'5',L'4',L'6',L'4',L'7',L'4',L'8',L'4',L'9', + L'5',L'0',L'5',L'1',L'5',L'2',L'5',L'3',L'5',L'4',L'5',L'5',L'5',L'6',L'5',L'7',L'5',L'8',L'5',L'9', + L'6',L'0',L'6',L'1',L'6',L'2',L'6',L'3',L'6',L'4',L'6',L'5',L'6',L'6',L'6',L'7',L'6',L'8',L'6',L'9', + L'7',L'0',L'7',L'1',L'7',L'2',L'7',L'3',L'7',L'4',L'7',L'5',L'7',L'6',L'7',L'7',L'7',L'8',L'7',L'9', + L'8',L'0',L'8',L'1',L'8',L'2',L'8',L'3',L'8',L'4',L'8',L'5',L'8',L'6',L'8',L'7',L'8',L'8',L'8',L'9', + L'9',L'0',L'9',L'1',L'9',L'2',L'9',L'3',L'9',L'4',L'9',L'5',L'9',L'6',L'9',L'7',L'9',L'8',L'9',L'9' +}; + // ^^^^^^^^^^ DERIVED FROM digit_table.h ^^^^^^^^^^ // vvvvvvvvvv DERIVED FROM d2s_full_table.h vvvvvvvvvv diff --git a/tests/std/tests/P0067R5_charconv/test.cpp b/tests/std/tests/P0067R5_charconv/test.cpp index 466a8d72d7c..f11365572a7 100644 --- a/tests/std/tests/P0067R5_charconv/test.cpp +++ b/tests/std/tests/P0067R5_charconv/test.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ #include "float_hex_precision_to_chars_test_cases.hpp" #include "float_scientific_precision_to_chars_test_cases.hpp" #include "float_to_chars_test_cases.hpp" +#include "wchar_test_cases.hpp" #include using namespace std; @@ -1077,6 +1079,40 @@ void test_right_shift_64_bits_with_rounding() { assert(_Right_shift_with_rounding(0xffff'ffff'ffff'ffffULL, 64, false) == 1); } +void wchar_tests() { + static_assert(size(__DIGIT_TABLE) == size(__DIGIT_TABLE)); + auto& fac = use_facet>(locale{}); + for (size_t i = 0; i < size(__DIGIT_TABLE); ++i) { + assert(fac.widen(__DIGIT_TABLE[i]) == __DIGIT_TABLE[i]); + } + + wchar_t buffer[32]; + for (const auto& t : double_to_wide_test_cases) { + auto result = __d2s_buffered_n(begin(buffer), end(buffer), t.value, t.fmt); + const wstring_view sv(t.correct); + assert(equal(buffer, result.first, sv.begin(), sv.end())); + } + + for (const auto& t : float_to_wide_test_cases) { + auto result = __f2s_buffered_n(begin(buffer), end(buffer), t.value, t.fmt); + const wstring_view sv(t.correct); + assert(equal(buffer, result.first, sv.begin(), sv.end())); + } + + for (const auto& t : wide_digit_pairs_test_cases) { + auto result = __d2fixed_buffered_n(begin(buffer), end(buffer), t.value, static_cast(t.precision)); + const wstring_view sv(t.correct); + assert(equal(buffer, result.first, sv.begin(), sv.end())); + } +} + +// GH-1569 - Test instantiation of wchar_t helpers. +template pair std::__to_chars( + wchar_t* const, wchar_t* const, const __floating_decimal_32, chars_format, const uint32_t, const uint32_t); +template pair std::__to_chars( + wchar_t* const, wchar_t* const, const __floating_decimal_64, chars_format, const double); +template pair std::__d2fixed_buffered_n(wchar_t*, wchar_t* const, const double, const uint32_t); + int main(int argc, char** argv) { const auto start = chrono::steady_clock::now(); @@ -1090,6 +1126,8 @@ int main(int argc, char** argv) { test_right_shift_64_bits_with_rounding(); + wchar_tests(); + const auto finish = chrono::steady_clock::now(); const long long ms = chrono::duration_cast(finish - start).count(); diff --git a/tests/std/tests/P0067R5_charconv/test.hpp b/tests/std/tests/P0067R5_charconv/test.hpp index b20c766395e..3d0275b9899 100644 --- a/tests/std/tests/P0067R5_charconv/test.hpp +++ b/tests/std/tests/P0067R5_charconv/test.hpp @@ -31,6 +31,12 @@ struct FloatToCharsTestCase { const char* correct; }; +struct FloatToWideTestCase { + float value; + chars_format fmt; + const wchar_t* correct; +}; + struct FloatPrecisionToCharsTestCase { float value; chars_format fmt; @@ -52,9 +58,22 @@ struct DoubleToCharsTestCase { const char* correct; }; +struct DoubleToWideTestCase { + double value; + chars_format fmt; + const wchar_t* correct; +}; + struct DoublePrecisionToCharsTestCase { double value; chars_format fmt; int precision; const char* correct; }; + +struct DoublePrecisionToWideTestCase { + double value; + chars_format fmt; + int precision; + const wchar_t* correct; +}; diff --git a/tests/std/tests/P0067R5_charconv/wchar_test_cases.hpp b/tests/std/tests/P0067R5_charconv/wchar_test_cases.hpp new file mode 100644 index 00000000000..795ac517987 --- /dev/null +++ b/tests/std/tests/P0067R5_charconv/wchar_test_cases.hpp @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#include + +#include "test.hpp" +using namespace std; + +// The wchar_t machinery is currently limited to a subset of the Ryu code. It is known to not handle: negative numbers, +// infinity, NaN, or hex formatting. + +inline constexpr DoublePrecisionToWideTestCase wide_digit_pairs_test_cases[] = { + {0.0001020304, chars_format::fixed, 10, L"0.0001020304"}, + {0.0506070809, chars_format::fixed, 10, L"0.0506070809"}, + {0.1011121314, chars_format::fixed, 10, L"0.1011121314"}, + {0.1516171819, chars_format::fixed, 10, L"0.1516171819"}, + {0.2021222324, chars_format::fixed, 10, L"0.2021222324"}, + {0.2526272829, chars_format::fixed, 10, L"0.2526272829"}, + {0.3031323334, chars_format::fixed, 10, L"0.3031323334"}, + {0.3536373839, chars_format::fixed, 10, L"0.3536373839"}, + {0.4041424344, chars_format::fixed, 10, L"0.4041424344"}, + {0.4546474849, chars_format::fixed, 10, L"0.4546474849"}, + {0.5051525354, chars_format::fixed, 10, L"0.5051525354"}, + {0.5556575859, chars_format::fixed, 10, L"0.5556575859"}, + {0.6061626364, chars_format::fixed, 10, L"0.6061626364"}, + {0.6566676869, chars_format::fixed, 10, L"0.6566676869"}, + {0.7071727374, chars_format::fixed, 10, L"0.7071727374"}, + {0.7576777879, chars_format::fixed, 10, L"0.7576777879"}, + {0.8081828384, chars_format::fixed, 10, L"0.8081828384"}, + {0.8586878889, chars_format::fixed, 10, L"0.8586878889"}, + {0.9091929394, chars_format::fixed, 10, L"0.9091929394"}, + {0.9596979899, chars_format::fixed, 10, L"0.9596979899"}, +}; + +inline constexpr DoubleToWideTestCase double_to_wide_test_cases[] = { + // Test special cases (zero, inf, nan) and an ordinary case. Also test negative signs. + {0.0, chars_format::scientific, L"0e+00"}, + //{-0.0, chars_format::scientific, L"-0e+00"}, + //{double_inf, chars_format::scientific, L"inf"}, + //{-double_inf, chars_format::scientific, L"-inf"}, + //{double_nan, chars_format::scientific, L"nan"}, + //{-double_nan, chars_format::scientific, L"-nan(ind)"}, + //{double_nan_payload, chars_format::scientific, L"nan"}, + //{-double_nan_payload, chars_format::scientific, L"-nan"}, + {2.018, chars_format::scientific, L"2.018e+00"}, + //{-2.018, chars_format::scientific, L"-2.018e+00"}, + {0.2018, chars_format::scientific, L"2.018e-01"}, + //{-0.2018, chars_format::scientific, L"-2.018e-01"}, + + // Ditto for fixed, which doesn't emit exponents. + {0.0, chars_format::fixed, L"0"}, + //{-0.0, chars_format::fixed, L"-0"}, + //{double_inf, chars_format::fixed, L"inf"}, + //{-double_inf, chars_format::fixed, L"-inf"}, + //{double_nan, chars_format::fixed, L"nan"}, + //{-double_nan, chars_format::fixed, L"-nan(ind)"}, + //{double_nan_payload, chars_format::fixed, L"nan"}, + //{-double_nan_payload, chars_format::fixed, L"-nan"}, + {2.018, chars_format::fixed, L"2.018"}, + //{-2.018, chars_format::fixed, L"-2.018"}, + + // Ditto for general, which selects fixed for the scientific exponent 0. + {0.0, chars_format::general, L"0"}, + //{-0.0, chars_format::general, L"-0"}, + //{double_inf, chars_format::general, L"inf"}, + //{-double_inf, chars_format::general, L"-inf"}, + //{double_nan, chars_format::general, L"nan"}, + //{-double_nan, chars_format::general, L"-nan(ind)"}, + //{double_nan_payload, chars_format::general, L"nan"}, + //{-double_nan_payload, chars_format::general, L"-nan"}, + {2.018, chars_format::general, L"2.018"}, + //{-2.018, chars_format::general, L"-2.018"}, + + // Ditto for plain, which selects fixed because it's shorter for these values. + {0.0, chars_format{}, L"0"}, + //{-0.0, chars_format{}, L"-0"}, + //{double_inf, chars_format{}, L"inf"}, + //{-double_inf, chars_format{}, L"-inf"}, + //{double_nan, chars_format{}, L"nan"}, + //{-double_nan, chars_format{}, L"-nan(ind)"}, + //{double_nan_payload, chars_format{}, L"nan"}, + //{-double_nan_payload, chars_format{}, L"-nan"}, + {2.018, chars_format{}, L"2.018"}, + //{-2.018, chars_format{}, L"-2.018"}, + + // Ditto for hex. + //{0.0, chars_format::hex, L"0p+0"}, + //{-0.0, chars_format::hex, L"-0p+0"}, + //{double_inf, chars_format::hex, L"inf"}, + //{-double_inf, chars_format::hex, L"-inf"}, + //{double_nan, chars_format::hex, L"nan"}, + //{-double_nan, chars_format::hex, L"-nan(ind)"}, + //{double_nan_payload, chars_format::hex, L"nan"}, + //{-double_nan_payload, chars_format::hex, L"-nan"}, + //{0x1.729p+0, chars_format::hex, L"1.729p+0"}, + //{-0x1.729p+0, chars_format::hex, L"-1.729p+0"}, + //{0x1.729p-1, chars_format::hex, L"1.729p-1"}, + //{-0x1.729p-1, chars_format::hex, L"-1.729p-1"}, +}; + +inline constexpr FloatToWideTestCase float_to_wide_test_cases[] = { + // Test special cases (zero, inf, nan) and an ordinary case. Also test negative signs. + {0.0f, chars_format::scientific, L"0e+00"}, + //{-0.0f, chars_format::scientific, L"-0e+00"}, + //{float_inf, chars_format::scientific, L"inf"}, + //{-float_inf, chars_format::scientific, L"-inf"}, + //{float_nan, chars_format::scientific, L"nan"}, + //{-float_nan, chars_format::scientific, L"-nan(ind)"}, + //{float_nan_payload, chars_format::scientific, L"nan"}, + //{-float_nan_payload, chars_format::scientific, L"-nan"}, + {2.018f, chars_format::scientific, L"2.018e+00"}, + //{-2.018f, chars_format::scientific, L"-2.018e+00"}, + {0.2018f, chars_format::scientific, L"2.018e-01"}, + //{-0.2018f, chars_format::scientific, L"-2.018e-01"}, + + // Ditto for fixed, which doesn't emit exponents. + {0.0f, chars_format::fixed, L"0"}, + //{-0.0f, chars_format::fixed, L"-0"}, + //{float_inf, chars_format::fixed, L"inf"}, + //{-float_inf, chars_format::fixed, L"-inf"}, + //{float_nan, chars_format::fixed, L"nan"}, + //{-float_nan, chars_format::fixed, L"-nan(ind)"}, + //{float_nan_payload, chars_format::fixed, L"nan"}, + //{-float_nan_payload, chars_format::fixed, L"-nan"}, + {2.018f, chars_format::fixed, L"2.018"}, + //{-2.018f, chars_format::fixed, L"-2.018"}, + + // Ditto for general, which selects fixed for the scientific exponent 0. + {0.0f, chars_format::general, L"0"}, + //{-0.0f, chars_format::general, L"-0"}, + //{float_inf, chars_format::general, L"inf"}, + //{-float_inf, chars_format::general, L"-inf"}, + //{float_nan, chars_format::general, L"nan"}, + //{-float_nan, chars_format::general, L"-nan(ind)"}, + //{float_nan_payload, chars_format::general, L"nan"}, + //{-float_nan_payload, chars_format::general, L"-nan"}, + {2.018f, chars_format::general, L"2.018"}, + //{-2.018f, chars_format::general, L"-2.018"}, + + // Ditto for plain, which selects fixed because it's shorter for these values. + {0.0f, chars_format{}, L"0"}, + //{-0.0f, chars_format{}, L"-0"}, + //{float_inf, chars_format{}, L"inf"}, + //{-float_inf, chars_format{}, L"-inf"}, + //{float_nan, chars_format{}, L"nan"}, + //{-float_nan, chars_format{}, L"-nan(ind)"}, + //{float_nan_payload, chars_format{}, L"nan"}, + //{-float_nan_payload, chars_format{}, L"-nan"}, + {2.018f, chars_format{}, L"2.018"}, + //{-2.018f, chars_format{}, L"-2.018"}, + + // Ditto for hex. + //{0.0f, chars_format::hex, L"0p+0"}, + //{-0.0f, chars_format::hex, L"-0p+0"}, + //{float_inf, chars_format::hex, L"inf"}, + //{-float_inf, chars_format::hex, L"-inf"}, + //{float_nan, chars_format::hex, L"nan"}, + //{-float_nan, chars_format::hex, L"-nan(ind)"}, + //{float_nan_payload, chars_format::hex, L"nan"}, + //{-float_nan_payload, chars_format::hex, L"-nan"}, + //{0x1.729p+0f, chars_format::hex, L"1.729p+0"}, + //{-0x1.729p+0f, chars_format::hex, L"-1.729p+0"}, + //{0x1.729p-1f, chars_format::hex, L"1.729p-1"}, + //{-0x1.729p-1f, chars_format::hex, L"-1.729p-1"}, +}; From 988f4becb85eabb0c5fad1ab1d2180581df8831a Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Sat, 6 Feb 2021 02:17:02 +0100 Subject: [PATCH 06/94] Add tests for basic_format_parse_context (#1597) * Noralize next_arg_id * Add precondition check to basic_format_parse_context::advance_to and debug optimization * ensure basic_format_parse_context::check_arg_id is not a constant expression * Add tests for basic_format_parse_context * Fix constant expression check * windsdk is bad * Use a non constexpr function call with a better name Co-authored-by: S. B. Tam * Reverse the polarity Co-authored-by: S. B. Tam Co-authored-by: Stephan T. Lavavej --- stl/inc/format | 43 +++++--- tests/std/test.lst | 1 + .../P0645R10_text_formatting_death/env.lst | 4 + .../P0645R10_text_formatting_death/test.cpp | 30 ++++++ .../env.lst | 2 +- .../test.cpp | 101 +++++++++++++++++- 6 files changed, 161 insertions(+), 20 deletions(-) create mode 100644 tests/std/tests/P0645R10_text_formatting_death/env.lst create mode 100644 tests/std/tests/P0645R10_text_formatting_death/test.cpp diff --git a/stl/inc/format b/stl/inc/format index 16466b6980d..f968858c563 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -305,7 +305,8 @@ constexpr const _CharT* _Parse_format_specs(const _CharT* _Begin, const _CharT* template struct formatter; -// TODO: test coverage +inline void _You_see_this_error_because_arg_id_is_out_of_range() noexcept {} + template class basic_format_parse_context { public: @@ -313,15 +314,6 @@ public: using const_iterator = typename basic_string_view<_CharT>::const_iterator; using iterator = const_iterator; -private: - basic_string_view<_CharT> _Format_string; - size_t _Num_args; - // The standard says this is size_t, however we use ptrdiff_t to save some space - // by not having to store the indexing mode. Below is a more detailed explanation - // of how this works. - ptrdiff_t _Next_arg_id = 0; - -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; @@ -333,8 +325,10 @@ public: _NODISCARD constexpr const_iterator end() const noexcept { return _Format_string.end(); } - constexpr void advance_to(const_iterator _It) { - _Format_string.remove_prefix(_It - begin()); + constexpr void advance_to(const const_iterator _It) { + _Adl_verify_range(_It, _Format_string.end()); + const auto _Diff = static_cast(_It._Unwrapped() - _Format_string._Unchecked_begin()); + _Format_string.remove_prefix(_Diff); } // While the standard presents an exposition only enum value for @@ -343,18 +337,33 @@ public: // _Next_arg_id > 0 means automatic // _Next_arg_id == -1 means manual constexpr size_t next_arg_id() { - if (_Next_arg_id >= 0) { - return _Next_arg_id++; + if (_Next_arg_id == -1) { + throw format_error("Can not switch from manual to automatic indexing"); } - throw format_error("Can not switch from manual to automatic indexing"); + + return static_cast(_Next_arg_id++); } - constexpr void check_arg_id(size_t _Id) { - (void) _Id; + + constexpr void check_arg_id(const size_t _Id) { + if (_STD is_constant_evaluated()) { + if (_Id >= _Num_args) { + _You_see_this_error_because_arg_id_is_out_of_range(); + } + } + if (_Next_arg_id > 0) { throw format_error("Can not switch from automatic to manual indexing"); } _Next_arg_id = -1; } + +private: + basic_string_view<_CharT> _Format_string; + size_t _Num_args; + // The standard says this is size_t, however we use ptrdiff_t to save some space + // by not having to store the indexing mode. Above is a more detailed explanation + // of how this works. + ptrdiff_t _Next_arg_id = 0; }; // TODO: test coverage diff --git a/tests/std/test.lst b/tests/std/test.lst index 94ca1188150..51f0673706c 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -251,6 +251,7 @@ tests\P0595R2_is_constant_evaluated tests\P0607R0_inline_variables tests\P0616R0_using_move_in_numeric tests\P0631R8_numbers_math_constants +tests\P0645R10_text_formatting_death tests\P0645R10_text_formatting_parse_contexts tests\P0645R10_text_formatting_parsing tests\P0660R10_jthread_and_cv_any diff --git a/tests/std/tests/P0645R10_text_formatting_death/env.lst b/tests/std/tests/P0645R10_text_formatting_death/env.lst new file mode 100644 index 00000000000..22f1f0230a4 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_death/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\strict_winsdk_concepts_matrix.lst diff --git a/tests/std/tests/P0645R10_text_formatting_death/test.cpp b/tests/std/tests/P0645R10_text_formatting_death/test.cpp new file mode 100644 index 00000000000..86e74da6037 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_death/test.cpp @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define _CONTAINER_DEBUG_LEVEL 1 + +#include +#include +#include + +#include +using namespace std; + +void test_case_advance_no_range() { + const auto format_string = "First {} and second {} and third {}"sv; + const auto other_format_string = "something"sv; + basic_format_parse_context context{format_string}; + context.advance_to(other_format_string.begin()); +} + +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec; + +#if _ITERATOR_DEBUG_LEVEL != 0 + exec.add_death_tests({ + test_case_advance_no_range, + }); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + + return exec.run(argc, argv); +} diff --git a/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst b/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst index f3ccc8613c6..22f1f0230a4 100644 --- a/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst +++ b/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst @@ -1,4 +1,4 @@ # Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -RUNALL_INCLUDE ..\concepts_matrix.lst +RUNALL_INCLUDE ..\strict_winsdk_concepts_matrix.lst diff --git a/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp b/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp index 7a3f5c0dad2..606f08b51f4 100644 --- a/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp @@ -2,11 +2,108 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include +#include +#include #include +#include #include +#include -// TODO: fill in tests +using namespace std; + +template +constexpr auto get_input() { + if constexpr (same_as) { + return "First {} and second {} and third {}"sv; + } else { + return L"First {} and second {} and third {}"sv; + } +} + +template +constexpr bool ensure_is_constant_expression(const bool should_be_constant_expression) { + basic_format_parse_context context{get_input(), 1}; + if (should_be_constant_expression) { + context.check_arg_id(0); + } else { + context.check_arg_id(1); + } + + return is_constant_evaluated(); +} + +const bool check_arg_id_is_constexpr_char = ensure_is_constant_expression(true); +const bool check_arg_id_not_constexpr_char = !ensure_is_constant_expression(false); +const bool check_arg_id_is_constexpr_wchar = ensure_is_constant_expression(true); +const bool check_arg_id_not_constexpr_wchar = !ensure_is_constant_expression(false); + +template +constexpr bool test_basic_format_parse_context() { + static_assert(!is_copy_constructible_v>); + static_assert(!is_copy_assignable_v>); + + const auto format_string = get_input(); + { // iterator interface + basic_format_parse_context context{format_string}; + const same_as::const_iterator> auto b = context.begin(); + assert(b == format_string.begin()); + static_assert(noexcept(context.begin())); + + const same_as::const_iterator> auto e = context.end(); + assert(e == format_string.end()); + static_assert(noexcept(context.end())); + + const auto new_position = format_string.begin() + 5; + context.advance_to(new_position); + assert(to_address(context.begin()) == to_address(new_position)); + assert(to_address(context.end()) == to_address(e)); + } + + { // arg_id + basic_format_parse_context context{format_string}; + const same_as auto first_arg_id = context.next_arg_id(); + assert(first_arg_id == 0); + + const same_as auto second_arg_id = context.next_arg_id(); + assert(second_arg_id == 1); + + if (!is_constant_evaluated()) { + try { + context.check_arg_id(0); + } catch (const format_error& e) { + assert(e.what() == "Can not switch from automatic to manual indexing"sv); + } + } + } + + { // check_arg_id + basic_format_parse_context context{format_string, 3}; + context.check_arg_id(0); + context.check_arg_id(1); + context.check_arg_id(2); // intentional duplicates to check whether this is ok to call multiple times + context.check_arg_id(1); + context.check_arg_id(0); + + if (!is_constant_evaluated()) { + try { + context.next_arg_id(); + } catch (const format_error& e) { + assert(e.what() == "Can not switch from manual to automatic indexing"sv); + } + } + } + + return true; +} int main() { - return 0; + test_basic_format_parse_context(); + test_basic_format_parse_context(); + static_assert(test_basic_format_parse_context()); + static_assert(test_basic_format_parse_context()); + + assert(check_arg_id_is_constexpr_char); + assert(check_arg_id_not_constexpr_char); + assert(check_arg_id_is_constexpr_wchar); + assert(check_arg_id_not_constexpr_wchar); } From a52214a211f4c840dd4d759c5b19f4004ed94320 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Fri, 12 Feb 2021 09:28:40 +0100 Subject: [PATCH 07/94] Implement a packed format store (#1625) Pack format_arg_store tightly. * Noralize next_arg_id * Add precondition check to basic_format_parse_context::advance_to and debug optimization * ensure basic_format_parse_context::check_arg_id is not a constant expression * Add tests for basic_format_parse_context * Fix constant expression check * Use a non constexpr function call with a better name Co-authored-by: S. B. Tam * Create a packed _Format_arg_store * We want bits not bytes * Dont use friendship * Remove all int128 code * Who doesnt hate max/min * Copy&Paste does not like ODR * Address review comments Co-authored by: Casey Carter * Minor fixes * Move make_format_args down * Use auto * Some more fixes * Everyone can be a wchar_t if he just believe in it * Add {unsigned} long tests * Int is in our hearts * Remove duplication * Move _CharType alias into function to unblock the build * First round of review comments * Use variable template rather than a function * Add warning * Make a proper constructor of _Format_arg_store * Use a single contiguous array to store the `_Format_arg_store` data * Specialize empty `_Format_arg_store` * More review comments * Fix uninitialized memory * Properly initialize the first Index Co-authored-by: S. B. Tam --- stl/inc/format | 369 ++++++++++++++++-- tests/std/test.lst | 1 + .../P0645R10_text_formatting_args/env.lst | 4 + .../P0645R10_text_formatting_args/test.cpp | 239 ++++++++++++ 4 files changed, 587 insertions(+), 26 deletions(-) create mode 100644 tests/std/tests/P0645R10_text_formatting_args/env.lst create mode 100644 tests/std/tests/P0645R10_text_formatting_args/test.cpp diff --git a/stl/inc/format b/stl/inc/format index f968858c563..83631ab8089 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -333,11 +333,11 @@ public: // While the standard presents an exposition only enum value for // the indexing mode (manual, automatic, or unknown) we use _Next_arg_id to indicate it. - // _Next_arg_id == 0 means unknown // _Next_arg_id > 0 means automatic - // _Next_arg_id == -1 means manual + // _Next_arg_id == 0 means unknown + // _Next_arg_id < 0 means manual constexpr size_t next_arg_id() { - if (_Next_arg_id == -1) { + if (_Next_arg_id < 0) { throw format_error("Can not switch from manual to automatic indexing"); } @@ -366,58 +366,365 @@ private: ptrdiff_t _Next_arg_id = 0; }; -// TODO: test coverage +enum class _Basic_format_arg_type : uint8_t { + _None, + _Int_type, + _UInt_type, + _Long_long_type, + _ULong_long_type, + _Bool_type, + _Char_type, + _Float_type, + _Double_type, + _Long_double_type, + _Pointer_type, + _CString_type, + _String_type, + _Custom_type, +}; +static_assert(static_cast(_Basic_format_arg_type::_Custom_type) <= 16); + template class basic_format_arg { public: - class handle; + using _CharType = typename _Context::char_type; -private: - using _Char_type = typename _Context::char_type; - - using _Format_arg_value = variant, handle>; - - _Format_arg_value _Value; - -public: class handle { private: const void* _Ptr; - void (*_Format)(basic_format_parse_context<_Char_type>& _Parse_ctx, _Context _Format_ctx, const void*); + void (*_Format)(basic_format_parse_context<_CharType>& _Parse_ctx, _Context _Format_ctx, const void*); friend basic_format_arg; public: - void format(basic_format_parse_context<_Char_type>& _Parse_ctx, _Context& _Format_ctx) { + template + explicit handle(const _Ty& _Val) noexcept + : _Ptr(_STD addressof(_Val)), + _Format([](basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx, const void* _Ptr) { + typename _Context::template formatter_type<_Ty> _Formatter; + _Parse_ctx.advance_to(_Formatter.parse(_Parse_ctx)); + _Format_ctx.advance_to(_Formatter.format(*static_cast(_Ptr), _Format_ctx)); + }) {} + + void format(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx) { _Format(_Parse_ctx, _Format_ctx, _Ptr); } }; - basic_format_arg() noexcept = default; + // TRANSITION, LLVM-49072 + basic_format_arg() noexcept : _Active_state(_Basic_format_arg_type::_None), _No_state() {} + + explicit basic_format_arg(const int _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Int_type), _Int_state(_Val) {} + explicit basic_format_arg(const unsigned int _Val) noexcept + : _Active_state(_Basic_format_arg_type::_UInt_type), _UInt_state(_Val) {} + explicit basic_format_arg(const long long _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Long_long_type), _Long_long_state(_Val) {} + explicit basic_format_arg(const unsigned long long _Val) noexcept + : _Active_state(_Basic_format_arg_type::_ULong_long_type), _ULong_long_state(_Val) {} + explicit basic_format_arg(const bool _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Bool_type), _Bool_state(_Val) {} + explicit basic_format_arg(const _CharType _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Char_type), _Char_state(_Val) {} + explicit basic_format_arg(const float _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Float_type), _Float_state(_Val) {} + explicit basic_format_arg(const double _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Double_type), _Double_state(_Val) {} + explicit basic_format_arg(const long double _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Long_double_type), _Long_double_state(_Val) {} + explicit basic_format_arg(const void* _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Pointer_type), _Pointer_state(_Val) {} + explicit basic_format_arg(const _CharType* _Val) noexcept + : _Active_state(_Basic_format_arg_type::_CString_type), _CString_state(_Val) {} + explicit basic_format_arg(const basic_string_view<_CharType> _Val) noexcept + : _Active_state(_Basic_format_arg_type::_String_type), _String_state(_Val) {} + explicit basic_format_arg(const handle _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Custom_type), _Custom_state(_Val) {} explicit operator bool() const noexcept { - return !_STD holds_alternative(_Value); + return _Active_state != _Basic_format_arg_type::_None; } + + _Basic_format_arg_type _Active_state = _Basic_format_arg_type::_None; + union { + monostate _No_state = monostate{}; + int _Int_state; + unsigned int _UInt_state; + long long _Long_long_state; + unsigned long long _ULong_long_state; + bool _Bool_state; + _CharType _Char_state; + float _Float_state; + double _Double_state; + long double _Long_double_state; + const void* _Pointer_state; + const _CharType* _CString_state; + basic_string_view<_CharType> _String_state; + handle _Custom_state; + }; }; -// TODO: test coverage +template +auto visit_format_arg(_Visitor&& _Vis, basic_format_arg<_Context> _Arg) { + switch (_Arg._Active_state) { + case _Basic_format_arg_type::_None: + return _Vis(_Arg._No_state); + case _Basic_format_arg_type::_Int_type: + return _Vis(_Arg._Int_state); + case _Basic_format_arg_type::_UInt_type: + return _Vis(_Arg._UInt_state); + case _Basic_format_arg_type::_Long_long_type: + return _Vis(_Arg._Long_long_state); + case _Basic_format_arg_type::_ULong_long_type: + return _Vis(_Arg._ULong_long_state); + case _Basic_format_arg_type::_Bool_type: + return _Vis(_Arg._Bool_state); + case _Basic_format_arg_type::_Char_type: + return _Vis(_Arg._Char_state); + case _Basic_format_arg_type::_Float_type: + return _Vis(_Arg._Float_state); + case _Basic_format_arg_type::_Double_type: + return _Vis(_Arg._Double_state); + case _Basic_format_arg_type::_Long_double_type: + return _Vis(_Arg._Long_double_state); + case _Basic_format_arg_type::_Pointer_type: + return _Vis(_Arg._Pointer_state); + case _Basic_format_arg_type::_CString_type: + return _Vis(_Arg._CString_state); + case _Basic_format_arg_type::_String_type: + return _Vis(_Arg._String_state); + case _Basic_format_arg_type::_Custom_type: + return _Vis(_Arg._Custom_state); + default: + _STL_VERIFY(false, "basic_format_arg is in impossible state"); + return _Vis(0); + } +} + +template +/* consteval */ constexpr auto _Get_format_arg_storage_type() noexcept { + using _CharType = typename _Context::char_type; + if constexpr (is_same_v<_Ty, monostate>) { + return monostate{}; + } else if constexpr (is_same_v<_Ty, _CharType>) { + return _CharType{}; + } else if constexpr (is_same_v<_Ty, char> && is_same_v<_CharType, wchar_t>) { + return _CharType{}; + } else if constexpr (signed_integral<_Ty> && sizeof(_Ty) <= sizeof(int)) { + return int{}; + } else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned int)) { + return static_cast(42); + } else if constexpr (signed_integral<_Ty> && sizeof(_Ty) <= sizeof(long long)) { + return static_cast(42); + } else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned long long)) { + return static_cast(42); + } else if constexpr (is_same_v<_Ty, bool>) { + return bool{}; + } else if constexpr (is_same_v<_Ty, float>) { + return float{}; + } else if constexpr (is_same_v<_Ty, double>) { + return double{}; + } else if constexpr (is_same_v<_Ty, long double>) { + return static_cast(42); + } else if constexpr (is_same_v<_Ty, const void*>) { + return static_cast(nullptr); + } else if constexpr (is_same_v<_Ty, const _CharType*>) { + return static_cast(nullptr); + } else if constexpr (is_same_v<_Ty, basic_string_view<_CharType>>) { + return basic_string_view<_CharType>{}; + } else { + return basic_format_arg<_Context>::handle(); + } +} + +template +inline constexpr size_t _Get_format_arg_storage_size = sizeof(_Get_format_arg_storage_type<_Context, _Ty>()); + +struct _Format_arg_store_packed_index { + // TRANSITION, Should be templated on number of arguments for even less storage + using _Index_type = size_t; + + constexpr _Format_arg_store_packed_index() = default; + constexpr explicit _Format_arg_store_packed_index(const size_t _Index) + : _Index(static_cast<_Index_type>(_Index)), _Type(_Basic_format_arg_type::_None) {} + + _Index_type _Index : (sizeof(_Index_type) * 8 - 4); + _Basic_format_arg_type _Type : 4; +}; + +template +class basic_format_args; + template class _Format_arg_store { - static constexpr size_t _Num_args = sizeof...(_Args); - // TODO: no arg packing yet +private: + using _CharType = typename _Context::char_type; + using _Index_type = _Format_arg_store_packed_index; + + friend basic_format_args<_Context>; + + static constexpr size_t _Num_args = sizeof...(_Args); + static constexpr size_t _Index_length = _Num_args * sizeof(_Index_type); + static constexpr size_t _Storage_length = (_Get_format_arg_storage_size<_Context, _Args> + ... + 0); + + // we store the data in memory as _Format_arg_store_packed_index[_Index_length] + unsigned char[_Storage_length] + unsigned char _Storage[_Index_length + _Storage_length]; + + template + void _Store_impl(const size_t _Arg_index, const _Basic_format_arg_type _Arg_type, _Ty _Val) noexcept { + const auto _Index_array = reinterpret_cast<_Index_type*>(_Storage); + const auto _Store_index = _Index_array[_Arg_index]._Index; + const auto _Length = _Get_format_arg_storage_size<_Context, _Ty>; + + _CSTD memcpy(_Storage + _Index_length + _Store_index, _STD addressof(_Val), _Length); + _Index_array[_Arg_index]._Type = _Arg_type; + if (_Arg_index + 1 < _Num_args) { + // Set the starting index of the next arg, as that is dynamic, must be called with increasing index + _Index_array[_Arg_index + 1] = _Format_arg_store_packed_index{_Store_index + _Length}; + } + } + + // See [format.arg]/5 + template + void _Store(const size_t _Arg_index, const _Ty& _Val) noexcept { + _Store_impl::handle>( + _Arg_index, _Basic_format_arg_type::_Custom_type, basic_format_arg<_Context>::handle(_Val)); + } + + // clang-format off + template + requires integral<_Ty> || floating_point<_Ty> + void _Store(const size_t _Arg_index, _Ty _Val) noexcept { + // clang-format on + if constexpr (is_same_v<_Ty, bool>) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Bool_type, _Val); + } else if constexpr (is_same_v<_Ty, _CharType>) { + _Store_impl<_CharType>(_Arg_index, _Basic_format_arg_type::_Char_type, _Val); + } else if constexpr (is_same_v<_Ty, char> && is_same_v<_CharType, wchar_t>) { + _Store_impl<_CharType>(_Arg_index, _Basic_format_arg_type::_Char_type, static_cast(_Val)); + } else if constexpr (signed_integral<_Ty> && sizeof(_Ty) <= sizeof(int)) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Int_type, static_cast(_Val)); + } else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned int)) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_UInt_type, static_cast(_Val)); + } else if constexpr (signed_integral<_Ty> && sizeof(_Ty) <= sizeof(long long)) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Long_long_type, static_cast(_Val)); + } else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned long long)) { + _Store_impl( + _Arg_index, _Basic_format_arg_type::_ULong_long_type, static_cast(_Val)); + } else if constexpr (is_same_v<_Ty, float>) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Float_type, _Val); + } else if constexpr (is_same_v<_Ty, double>) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Double_type, _Val); + } else if constexpr (is_same_v<_Ty, long double>) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Long_double_type, _Val); + } else { + static_assert(_Always_false<_Ty>, "Invalid type passed to _Format_arg_store::_Store"); + } + } - using _Value_type = basic_format_arg<_Context>; + void _Store(const size_t _Arg_index, const _CharType* _Val) noexcept { + _Store_impl(_Arg_index, _Basic_format_arg_type::_CString_type, _Val); + } + + template + void _Store(const size_t _Arg_index, basic_string_view<_CharType, _Traits> _Val) noexcept { + _Store_impl>( + _Arg_index, _Basic_format_arg_type::_String_type, basic_string_view<_CharType>{_Val}); + } + + template + void _Store(const size_t _Arg_index, const basic_string<_CharType, _Traits, _Alloc>& _Val) noexcept { + _Store_impl>( + _Arg_index, _Basic_format_arg_type::_String_type, basic_string_view<_CharType>{_Val.data(), _Val.size()}); + } + + void _Store(const size_t _Arg_index, nullptr_t) noexcept { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Pointer_type, static_cast(nullptr)); + } + + // clang-format off + template + requires is_void_v<_Ty> + void _Store(const size_t _Arg_index, const _Ty* _Ptr) noexcept { + // clang-format on + _Store_impl(_Arg_index, _Basic_format_arg_type::_Pointer_type, static_cast(_Ptr)); + } + +public: + _Format_arg_store(const _Args&... _Vals) noexcept { + // Note: _Storage is uninitialized, so manually initialize the first index + _STD construct_at(reinterpret_cast<_Format_arg_store_packed_index*>(_Storage)); + size_t _Arg_index = 0; + (_Store(_Arg_index++, _Vals), ...); + } }; -// TODO: test coverage +template +class _Format_arg_store<_Context> {}; + template class basic_format_args { +private: + template + _NODISCARD static auto _Get_value_from_memory(const unsigned char* _Val) noexcept { + auto& _Temp = *reinterpret_cast(_Val); + return _Bit_cast<_Ty>(_Temp); + } + public: basic_format_args() noexcept; + basic_format_args(const _Format_arg_store<_Context>&) noexcept {} template - basic_format_args(const _Format_arg_store<_Context, _Args...>& store) noexcept; + basic_format_args(const _Format_arg_store<_Context, _Args...>& _Store) noexcept + : _Num_args(sizeof...(_Args)), _Storage(_Store._Storage) {} + + basic_format_arg<_Context> get(const size_t _Index) const noexcept { + if (_Index >= _Num_args) { + return basic_format_arg<_Context>{}; + } - basic_format_arg<_Context> get(size_t _Index) const noexcept; + using _CharType = typename _Context::char_type; + const auto _Packed_index = reinterpret_cast(_Storage)[_Index]; + const auto _Index_length = _Num_args * sizeof(_Format_arg_store_packed_index); + const unsigned char* _Arg_storage = _Storage + _Index_length + _Packed_index._Index; + + switch (_Packed_index._Type) { + case _Basic_format_arg_type::_None: + return basic_format_arg<_Context>{}; + case _Basic_format_arg_type::_Int_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_UInt_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Long_long_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_ULong_long_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Bool_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Char_type: + return basic_format_arg<_Context>{_Get_value_from_memory<_CharType>(_Arg_storage)}; + case _Basic_format_arg_type::_Float_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Double_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Long_double_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Pointer_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_CString_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_String_type: + return basic_format_arg<_Context>{_Get_value_from_memory>(_Arg_storage)}; + case _Basic_format_arg_type::_Custom_type: + return basic_format_arg<_Context>{ + _Get_value_from_memory::handle>(_Arg_storage)}; + default: + _STL_ASSERT(false, "Invalid basic_format_arg type"); + return basic_format_arg<_Context>{}; + } + } + +private: + size_t _Num_args = 0; + const unsigned char* _Storage = nullptr; }; // TODO: test coverage @@ -459,6 +766,16 @@ using wformat_context = basic_format_context, wstr using format_args = basic_format_args; using wformat_args = basic_format_args; +template +auto make_format_args(const _Args&... _Vals) { + return _Format_arg_store<_Context, _Args...>{_Vals...}; +} + +template +auto make_wformat_args(const _Args&... _Vals) { + return _Format_arg_store<_Context, _Args...>{_Vals...}; +} + // FUNCTION vformat string vformat(string_view _Fmt, format_args _Args); wstring vformat(wstring_view _Fmt, wformat_args _Args); diff --git a/tests/std/test.lst b/tests/std/test.lst index 51f0673706c..1b777a88756 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -251,6 +251,7 @@ tests\P0595R2_is_constant_evaluated tests\P0607R0_inline_variables tests\P0616R0_using_move_in_numeric tests\P0631R8_numbers_math_constants +tests\P0645R10_text_formatting_args tests\P0645R10_text_formatting_death tests\P0645R10_text_formatting_parse_contexts tests\P0645R10_text_formatting_parsing diff --git a/tests/std/tests/P0645R10_text_formatting_args/env.lst b/tests/std/tests/P0645R10_text_formatting_args/env.lst new file mode 100644 index 00000000000..22f1f0230a4 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_args/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\strict_winsdk_concepts_matrix.lst diff --git a/tests/std/tests/P0645R10_text_formatting_args/test.cpp b/tests/std/tests/P0645R10_text_formatting_args/test.cpp new file mode 100644 index 00000000000..08355b8a90f --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_args/test.cpp @@ -0,0 +1,239 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +template +constexpr auto get_input_literal() { + if constexpr (same_as) { + return "meow"; + } else { + return L"meow"; + } +} + +template +constexpr auto get_input_sv() { + if constexpr (same_as) { + return "meow"sv; + } else { + return L"meow"sv; + } +} + +class context { +public: + using char_type = char; +}; + +class wcontext { +public: + using char_type = wchar_t; +}; + +enum class Arg_type : uint8_t { + none, + int_type, + unsigned_type, + long_long_type, + unsigned_long_long_type, + bool_type, + char_type, + float_type, + double_type, + long_double_type, + pointer_type, + string_literal_type, + string_type, + handle_type, +}; + +template +auto visitor = [](auto&& arg) { + using T = decay_t; + using char_type = typename Context::char_type; + if constexpr (is_same_v) { + return Arg_type::none; + } else if constexpr (is_same_v) { + return Arg_type::int_type; + } else if constexpr (is_same_v) { + return Arg_type::unsigned_type; + } else if constexpr (is_same_v) { + return Arg_type::long_long_type; + } else if constexpr (is_same_v) { + return Arg_type::unsigned_long_long_type; + } else if constexpr (is_same_v) { + return Arg_type::char_type; + } else if constexpr (is_same_v) { + return Arg_type::float_type; + } else if constexpr (is_same_v) { + return Arg_type::double_type; + } else if constexpr (is_same_v) { + return Arg_type::long_double_type; + } else if constexpr (is_same_v) { + return Arg_type::pointer_type; + } else if constexpr (is_same_v) { + return Arg_type::string_literal_type; + } else if constexpr (is_same_v>) { + return Arg_type::string_type; + } else { + return Arg_type::handle_type; + } +}; + +template +void test_basic_format_arg() { + using char_type = typename Context::char_type; + + { // construction + basic_format_arg default_constructed; + assert(!default_constructed); + + basic_format_arg from_int{5}; + assert(from_int); + + basic_format_arg from_unsigned{5u}; + assert(from_unsigned); + + basic_format_arg from_long_long{5ll}; + assert(from_long_long); + + basic_format_arg from_unsigned_long_long{5ull}; + assert(from_unsigned_long_long); + + basic_format_arg from_float{5.0f}; + assert(from_float); + + basic_format_arg from_double{5.0}; + assert(from_double); + + basic_format_arg from_long_double{5.0L}; + assert(from_long_double); + + basic_format_arg from_pointer{static_cast(nullptr)}; + assert(from_pointer); + + basic_format_arg from_literal{get_input_literal()}; + assert(from_literal); + + basic_format_arg from_string_view{get_input_sv()}; + assert(from_string_view); + + // TRANSITION, implement handle + // basic_format_arg from_handle{}; + // assert(from_handle); + } +} +template +void test_empty_format_arg() { + const auto store = make_format_args(); + const basic_format_args args{store}; + const same_as> auto first_arg = args.get(0); + assert(!first_arg); +} + +template +void test_single_format_arg(Type value) { + const auto store = make_format_args(value); + const basic_format_args args{store}; + const same_as> auto first_arg = args.get(0); + assert(first_arg); + assert(visit_format_arg(visitor, first_arg) == Result); + const same_as> auto other_arg = args.get(1); + assert(!other_arg); +} + +template +void test_format_arg_store() { + using char_type = typename Context::char_type; + + test_empty_format_arg(); + + test_single_format_arg(42); + if constexpr (is_same_v) { + test_single_format_arg(42); + } else { + test_single_format_arg(42); + } + test_single_format_arg(42); + test_single_format_arg(42); +#ifdef __cpp_char8_t + test_single_format_arg(42); +#endif // __cpp_char8_t + test_single_format_arg(42); + test_single_format_arg(42); + + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + if constexpr (sizeof(int) == sizeof(ptrdiff_t)) { + test_single_format_arg(42); + } else { + test_single_format_arg(42); + } + + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + if constexpr (is_same_v) { + test_single_format_arg(42); + } else { + test_single_format_arg(42); + } + test_single_format_arg(42); + if constexpr (is_same_v) { + test_single_format_arg(42); + } else { + test_single_format_arg(42); + } + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + if constexpr (sizeof(unsigned int) == sizeof(size_t)) { + test_single_format_arg(42); + } else { + test_single_format_arg(42); + } + + test_single_format_arg(42.f); + test_single_format_arg(42.); + test_single_format_arg(42.); + + test_single_format_arg(nullptr); + + test_single_format_arg(get_input_literal()); + test_single_format_arg, Arg_type::string_type>(get_input_sv()); +} + +int main() { + test_basic_format_arg(); + test_basic_format_arg(); + test_format_arg_store(); + test_format_arg_store(); +} From 286fe4688118a9337cf1c1248c0974bc791a8b31 Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Thu, 18 Feb 2021 13:02:22 -0800 Subject: [PATCH 08/94] std::format parsing callbacks and machinery (#1616) * add parse_precision. * adopt style review comments. * actually call the test functions. * add parse_precision. * adopt style review comments. * add parse format spec * add tests for parse_format_specs * initial commit for string_parsing * remove duplicate test frunctions * on_text and some on_format_specs * add basic_format_specs and the formatter dispatcher. * forgot a semicolon. * now we need to do _Write... :| * more work on format checkers * add _Format_arg_type_to_enum * add char* writer * make it compile * flesh out format_args_store. * don't need overloaded * get rid of a bunch of declvals. * compile vformat (does not link) * make width and precision unsigned int internally (instead of int) * add some comments for shat the various helpery classes actually do. * formatting (heh) fixes. * uncomment and fix requirement for _Parse_replacement_field_callbacks * don't write out the entire format string when encountering a replacement field. * resolve some code review comments * fix merge artifacts, convert from _Type to _Basic_format_arg_type * address code review comments * alphabatize test.lst * static_cast(0) instead of unsigned{} * remove dead _STL_INTERNAL_CHECK and find in the correct range Co-authored-by: Charles Barto --- stl/inc/format | 832 ++++++++++++++---- tests/std/test.lst | 1 + .../env.lst | 4 + .../test.cpp | 16 + .../P0645R10_text_formatting_parsing/test.cpp | 62 +- 5 files changed, 725 insertions(+), 190 deletions(-) create mode 100644 tests/std/tests/P0645R10_text_formatting_formatting/env.lst create mode 100644 tests/std/tests/P0645R10_text_formatting_formatting/test.cpp diff --git a/stl/inc/format b/stl/inc/format index 83631ab8089..65b9145847a 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -12,6 +12,7 @@ #pragma message("The contents of are available only with C++20 concepts support.") #else // ^^^ !defined(__cpp_lib_concepts) / defined(__cpp_lib_concepts) vvv +#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) @@ -38,6 +40,30 @@ enum class _Align { _None, _Left, _Right, _Center }; enum class _Sign { _None, _Plus, _Minus, _Space }; +enum class _Basic_format_arg_type : uint8_t { + _None, + _Int_type, + _UInt_type, + _Long_long_type, + _ULong_long_type, + _Bool_type, + _Char_type, + _Float_type, + _Double_type, + _Long_double_type, + _Pointer_type, + _CString_type, + _String_type, + _Custom_type, +}; +static_assert(static_cast(_Basic_format_arg_type::_Custom_type) <= 16); + +constexpr bool _Is_integral_fmt_type(_Basic_format_arg_type _Ty) { + return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_ULong_long_type; +} +constexpr bool _Is_arithmetic_fmt_type(_Basic_format_arg_type _Ty) { + return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_Long_double_type; +} struct _Auto_id_tag {}; // clang-format off @@ -45,29 +71,218 @@ template concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln, _Sign _Sgn) { { _At._On_align(_Aln) } -> same_as; { _At._On_fill(_Sv) } -> same_as; - { _At._On_width(_STD declval()) } -> same_as; - { _At._On_dynamic_width(_STD declval()) } -> same_as; - { _At._On_dynamic_width(_STD declval<_Auto_id_tag>()) } -> same_as; - { _At._On_precision(_STD declval()) } -> same_as; - { _At._On_dynamic_precision(_STD declval()) } -> same_as; - { _At._On_dynamic_precision(_STD declval<_Auto_id_tag>()) } -> same_as; + { _At._On_width(static_cast(0)) } -> same_as; + { _At._On_dynamic_width(size_t{}) } -> same_as; + { _At._On_dynamic_width(_Auto_id_tag{}) } -> same_as; + { _At._On_precision(static_cast(0)) } -> same_as; + { _At._On_dynamic_precision(size_t{}) } -> same_as; + { _At._On_dynamic_precision(_Auto_id_tag{}) } -> same_as; { _At._On_sign(_Sgn) } -> same_as; { _At._On_hash() } -> same_as; { _At._On_zero() } -> same_as; - { _At._On_type(_STD declval<_CharT>()) } -> same_as; + { _At._On_type(_CharT{}) } -> same_as; }; template concept _Parse_arg_id_callbacks = requires(_Ty _At) { { _At._On_auto_id() } -> same_as; - { _At._On_manual_id(_STD declval()) } -> same_as; + { _At._On_manual_id(size_t{}) } -> same_as; }; + +template +concept _Parse_replacement_field_callbacks = requires(_Ty _At, const _CharT* _Begin, const _CharT* _End) { + { _At._Parse_context }; + { _At._On_text(_Begin, _End) } -> same_as; + { _At._On_replacement_field(size_t{}, static_cast(nullptr)) } -> same_as; + { _At._On_format_specs(size_t{}, _Begin, _End) } -> same_as; +}; + // clang-format on +template +struct formatter; + +inline void _You_see_this_error_because_arg_id_is_out_of_range() noexcept {} + +template +class basic_format_parse_context { +public: + using char_type = _CharT; + using const_iterator = typename basic_string_view<_CharT>::const_iterator; + using iterator = const_iterator; + + 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; + + _NODISCARD constexpr const_iterator begin() const noexcept { + return _Format_string.begin(); + } + _NODISCARD constexpr const_iterator end() const noexcept { + return _Format_string.end(); + } + constexpr void advance_to(const const_iterator _It) { + _Adl_verify_range(_It, _Format_string.end()); + // _It must be after _Format_string.begin(). + const auto _Diff = static_cast(_It._Unwrapped() - _Format_string._Unchecked_begin()); + _Format_string.remove_prefix(_Diff); + } + + // While the standard presents an exposition-only enum value for + // the indexing mode (manual, automatic, or unknown) we use _Next_arg_id to indicate it. + // _Next_arg_id > 0 means automatic + // _Next_arg_id == 0 means unknown + // _Next_arg_id < 0 means manual + constexpr size_t next_arg_id() { + if (_Next_arg_id < 0) { + throw format_error("Can not switch from manual to automatic indexing"); + } + + return static_cast(_Next_arg_id++); + } + + constexpr void check_arg_id(const size_t _Id) { + if (_STD is_constant_evaluated()) { + if (_Id >= _Num_args) { + _You_see_this_error_because_arg_id_is_out_of_range(); + } + } + + if (_Next_arg_id > 0) { + throw format_error("Can not switch from automatic to manual indexing"); + } + _Next_arg_id = -1; + } + +private: + basic_string_view<_CharT> _Format_string; + size_t _Num_args; + // The standard says this is size_t, however we use ptrdiff_t to save some space + // by not having to store the indexing mode. Above is a more detailed explanation + // of how this works. + ptrdiff_t _Next_arg_id = 0; +}; + +template +class basic_format_arg { +public: + using _CharType = typename _Context::char_type; + + class handle { + private: + const void* _Ptr; + void (*_Format)(basic_format_parse_context<_CharType>& _Parse_ctx, _Context _Format_ctx, const void*); + friend basic_format_arg; + + public: + template + explicit handle(const _Ty& _Val) noexcept + : _Ptr(_STD addressof(_Val)), + _Format([](basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx, const void* _Ptr) { + typename _Context::template formatter_type<_Ty> _Formatter; + _Parse_ctx.advance_to(_Formatter.parse(_Parse_ctx)); + _Format_ctx.advance_to(_Formatter.format(*static_cast(_Ptr), _Format_ctx)); + }) {} + + void format(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx) { + _Format(_Parse_ctx, _Format_ctx, _Ptr); + } + }; + + // TRANSITION, LLVM-49072 + basic_format_arg() noexcept : _Active_state(_Basic_format_arg_type::_None), _No_state() {} + + explicit basic_format_arg(const int _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Int_type), _Int_state(_Val) {} + explicit basic_format_arg(const unsigned int _Val) noexcept + : _Active_state(_Basic_format_arg_type::_UInt_type), _UInt_state(_Val) {} + explicit basic_format_arg(const long long _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Long_long_type), _Long_long_state(_Val) {} + explicit basic_format_arg(const unsigned long long _Val) noexcept + : _Active_state(_Basic_format_arg_type::_ULong_long_type), _ULong_long_state(_Val) {} + explicit basic_format_arg(const bool _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Bool_type), _Bool_state(_Val) {} + explicit basic_format_arg(const _CharType _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Char_type), _Char_state(_Val) {} + explicit basic_format_arg(const float _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Float_type), _Float_state(_Val) {} + explicit basic_format_arg(const double _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Double_type), _Double_state(_Val) {} + explicit basic_format_arg(const long double _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Long_double_type), _Long_double_state(_Val) {} + explicit basic_format_arg(const void* _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Pointer_type), _Pointer_state(_Val) {} + explicit basic_format_arg(const _CharType* _Val) noexcept + : _Active_state(_Basic_format_arg_type::_CString_type), _CString_state(_Val) {} + explicit basic_format_arg(const basic_string_view<_CharType> _Val) noexcept + : _Active_state(_Basic_format_arg_type::_String_type), _String_state(_Val) {} + explicit basic_format_arg(const handle _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Custom_type), _Custom_state(_Val) {} + explicit operator bool() const noexcept { + return _Active_state != _Basic_format_arg_type::_None; + } + + _Basic_format_arg_type _Active_state = _Basic_format_arg_type::_None; + union { + monostate _No_state = monostate{}; + int _Int_state; + unsigned int _UInt_state; + long long _Long_long_state; + unsigned long long _ULong_long_state; + bool _Bool_state; + _CharType _Char_state; + float _Float_state; + double _Double_state; + long double _Long_double_state; + const void* _Pointer_state; + const _CharType* _CString_state; + basic_string_view<_CharType> _String_state; + handle _Custom_state; + }; +}; + +template +auto visit_format_arg(_Visitor&& _Vis, basic_format_arg<_Context> _Arg) { + switch (_Arg._Active_state) { + case _Basic_format_arg_type::_None: + return _Vis(_Arg._No_state); + case _Basic_format_arg_type::_Int_type: + return _Vis(_Arg._Int_state); + case _Basic_format_arg_type::_UInt_type: + return _Vis(_Arg._UInt_state); + case _Basic_format_arg_type::_Long_long_type: + return _Vis(_Arg._Long_long_state); + case _Basic_format_arg_type::_ULong_long_type: + return _Vis(_Arg._ULong_long_state); + case _Basic_format_arg_type::_Bool_type: + return _Vis(_Arg._Bool_state); + case _Basic_format_arg_type::_Char_type: + return _Vis(_Arg._Char_state); + case _Basic_format_arg_type::_Float_type: + return _Vis(_Arg._Float_state); + case _Basic_format_arg_type::_Double_type: + return _Vis(_Arg._Double_state); + case _Basic_format_arg_type::_Long_double_type: + return _Vis(_Arg._Long_double_state); + case _Basic_format_arg_type::_Pointer_type: + return _Vis(_Arg._Pointer_state); + case _Basic_format_arg_type::_CString_type: + return _Vis(_Arg._CString_state); + case _Basic_format_arg_type::_String_type: + return _Vis(_Arg._String_state); + case _Basic_format_arg_type::_Custom_type: + return _Vis(_Arg._Custom_state); + default: + _STL_VERIFY(false, "basic_format_arg is in impossible state"); + return _Vis(0); + } +} + // we need to implement this ourselves because from_chars does not work with wide characters template -constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _CharT* _End, int& _Integer) { +constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _CharT* _End, unsigned int& _Value) { _STL_INTERNAL_CHECK(_Begin != _End && '0' <= *_Begin && *_Begin <= '9'); - unsigned int _Value = 0; + _Value = 0; constexpr unsigned int _Max_int = static_cast((numeric_limits::max)()); constexpr unsigned int _Big_int = _Max_int / 10; @@ -82,7 +297,6 @@ constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _ if (_Value > _Max_int) { throw format_error("Number is too big"); } - _Integer = static_cast(_Value); return _Begin; } @@ -97,7 +311,7 @@ constexpr const _CharT* _Parse_arg_id(const _CharT* _Begin, const _CharT* _End, } if (_Ch >= '0' && _Ch <= '9') { - int _Index = 0; + unsigned int _Index = 0; // arg_id is not allowed to have any leading zeros, but is allowed to be // equal to zero (but not '00'). So if _Ch is zero we skip the parsing, leave // _Index set to zero and let the validity checks below ensure that the arg_id @@ -125,7 +339,9 @@ constexpr const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, _ _STL_INTERNAL_CHECK(_Begin != _End && *_Begin != '}'); // align and fill auto _Parsed_align = _Align::_None; - auto _Align_pt = _Begin + 1; + + // TODO: should increment one code point + auto _Align_pt = _Begin + 1; if (_Align_pt == _End) { _Align_pt = _Begin; } @@ -172,7 +388,7 @@ struct _Width_adapter { constexpr void _On_auto_id() { _Callbacks._On_dynamic_width(_Auto_id_tag{}); } - constexpr void _On_manual_id(int _Id) { + constexpr void _On_manual_id(size_t _Id) { _Callbacks._On_dynamic_width(_Id); } }; @@ -188,17 +404,36 @@ struct _Precision_adapter { constexpr void _On_auto_id() { _Callbacks._On_dynamic_precision(_Auto_id_tag{}); } - constexpr void _On_manual_id(int _Id) { + constexpr void _On_manual_id(size_t _Id) { _Callbacks._On_dynamic_precision(_Id); } }; +// _Parse_arg_id expects a handler when it finds an argument id, however +// _Parse_replacement_field actually needs to know the value of that argument ID to pass on +// to _Handler._On_replacement_field or _Handler._On_format_specs. This _Parse_arg_id wrapper +// stores the value of the arg id for later use, so _Parse_replacement_field has access to it. +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)); + } + constexpr void _On_manual_id(size_t _Id) { + _Parse_context.check_arg_id(_Id); + _Arg_id = _Id; + _STL_INTERNAL_CHECK(_Arg_id != static_cast(-1)); + } +}; + template _Callbacks_type> constexpr const _CharT* _Parse_width(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { _STL_INTERNAL_CHECK(_Begin != _End); if ('1' <= *_Begin && *_Begin <= '9') { - int _Value = 0; - _Begin = _Parse_nonnegative_integer(_Begin, _End, _Value); + unsigned int _Value = 0; + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Value); _Callbacks._On_width(_Value); } else if (*_Begin == '{') { ++_Begin; @@ -222,8 +457,8 @@ constexpr const _CharT* _Parse_precision(const _CharT* _Begin, const _CharT* _En } if ('0' <= _Ch && _Ch <= '9') { - int _Precision = 0; - _Begin = _Parse_nonnegative_integer(_Begin, _End, _Precision); + unsigned int _Precision = 0; + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Precision); _Callbacks._On_precision(_Precision); } else if (_Ch == '{') { ++_Begin; @@ -302,202 +537,307 @@ constexpr const _CharT* _Parse_format_specs(const _CharT* _Begin, const _CharT* return _Begin; } -template -struct formatter; +template _HandlerT> +constexpr const _CharT* _Parse_replacement_field(const _CharT* _Begin, const _CharT* _End, _HandlerT&& _Handler) { + ++_Begin; + if (_Begin == _End) { + throw format_error("Invalid format string."); + } + + if (*_Begin == '}') { + // string was "{}", and we have a replacement field + _Handler._On_replacement_field(_Handler._Parse_context.next_arg_id(), _Begin); + } else if (*_Begin == '{') { + // string was "{{", so we have a literal "{" to print + _Handler._On_text(_Begin, _Begin + 1); + } else { + _Id_adapter<_CharT> _Adapter{_Handler._Parse_context}; + _Begin = _Parse_arg_id(_Begin, _End, _Adapter); + _CharT _Ch = _CharT{}; + if (_Begin != _End) { + _Ch = *_Begin; + } + if (_Ch == '}') { + _Handler._On_replacement_field(_Adapter._Arg_id, _Begin); + } else if (_Ch == ':') { + _Begin = _Handler._On_format_specs(_Adapter._Arg_id, _Begin + 1, _End); + if (_Begin == _End || *_Begin != '}') { + throw format_error("Unknown format specifier."); + } + } else { + throw format_error("Missing '}' in format string."); + } + } + return _Begin + 1; +} + +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(); + while (_Begin != _End) { + const _CharT* _OpeningCurl = _Begin; + if (*_Begin != '{') { + // we didn't start at an opening curl, find the next one + _OpeningCurl = _STD find(_Begin + 1, _End, '{'); + for (;;) { + const _CharT* _ClosingCurl = _Find_unchecked(_Begin, _OpeningCurl, '}'); + + // In this case we didn't find either a closing curl or opening curl. + // Write the whole thing out. + if (_ClosingCurl == _OpeningCurl) { + return _Handler._On_text(_Begin, _OpeningCurl); + } + // We know _ClosingCurl isn't past the end because + // the above condition was not met. + ++_ClosingCurl; + if (_ClosingCurl == _OpeningCurl || *_ClosingCurl != '}') { + throw format_error("Unmatched '}' in format string."); + } + // We found two closing curls, so output only one of them + _Handler._On_text(_Begin, _ClosingCurl); + + // skip over the second closing curl + _Begin = _ClosingCurl + 1; + } + + // We are done, there were no replacement fields. + if (_OpeningCurl == _End) { + return; + } + } + // Parse the replacement field starting at _OpeningCurl and ending sometime before _End. + _Begin = _Parse_replacement_field(_OpeningCurl, _End, _Handler); + } +} -inline void _You_see_this_error_because_arg_id_is_out_of_range() noexcept {} template -class basic_format_parse_context { +struct _Basic_format_specs { + unsigned int _Width; + unsigned int _Precision; + char _Type; + _Align _Alignment; + _Sign _Sgn; + bool _Alt; + // At most one codepoint (so one char32_t or four utf-8 char8_t). + _CharT _Fill[4]; +}; + +// Model of _Parse_specs_callbacks that fills a _Basic_format_specs with the parsed data. +template +class _Specs_setter { public: - using char_type = _CharT; - using const_iterator = typename basic_string_view<_CharT>::const_iterator; - using iterator = const_iterator; + explicit constexpr _Specs_setter(_Basic_format_specs<_CharT> _Specs_) : _Specs(_Specs_) {} - 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; + constexpr void _On_align(_Align _Aln) { + _Specs._Alignment = _Aln; + } - _NODISCARD constexpr const_iterator begin() const noexcept { - return _Format_string.begin(); + constexpr void _On_fill(basic_string_view<_CharT> _Sv) { + if (_Sv.size() > 4) { + throw format_error("Invalid fill (too long)."); + } + _STD fill(_Specs._Fill, _Specs._Fill + 4, _CharT{}); + _STD copy(_Sv.begin(), _Sv.end(), _Specs._Fill); } - _NODISCARD constexpr const_iterator end() const noexcept { - return _Format_string.end(); + + constexpr void _On_sign(_Sign _Sgn) { + _Specs._Sgn = _Sgn; } - constexpr void advance_to(const const_iterator _It) { - _Adl_verify_range(_It, _Format_string.end()); - const auto _Diff = static_cast(_It._Unwrapped() - _Format_string._Unchecked_begin()); - _Format_string.remove_prefix(_Diff); + + constexpr void _On_hash() { + _Specs._Alt = true; } - // While the standard presents an exposition only enum value for - // the indexing mode (manual, automatic, or unknown) we use _Next_arg_id to indicate it. - // _Next_arg_id > 0 means automatic - // _Next_arg_id == 0 means unknown - // _Next_arg_id < 0 means manual - constexpr size_t next_arg_id() { - if (_Next_arg_id < 0) { - throw format_error("Can not switch from manual to automatic indexing"); - } + constexpr void _On_zero() { + _Specs._Alignment = _Align::_None; + _Specs._Fill[0] = _CharT{'0'}; + } - return static_cast(_Next_arg_id++); + constexpr void _On_width(unsigned int _Width) { + _Specs._Width = _Width; } - constexpr void check_arg_id(const size_t _Id) { - if (_STD is_constant_evaluated()) { - if (_Id >= _Num_args) { - _You_see_this_error_because_arg_id_is_out_of_range(); + constexpr void _On_precision(unsigned int _Precision) { + _Specs._Precision = _Precision; + } + + constexpr void _On_type(_CharT _Type) { + _Specs._Type = static_cast<_CharT>(_Type); + } + +protected: + _Basic_format_specs<_CharT> _Specs; +}; + +template +constexpr basic_format_arg<_Context> _Get_arg(_Context _Ctx, size_t _Arg_id) { + // note: while this is parameterized on the _Arg_id type in libfmt we don't + // need to do that in std::format because it's only called with either an integer + // id or a named id (which we do not support in std::format) + auto _Arg = _Ctx.arg(_Arg_id); + if (!_Arg) { + throw format_error("Argument not found."); + } + return _Arg; +} + +// Checks that the type and value of an argument associated with a dynamic +// width specifier are valid. +class _Width_checker { +public: + template + constexpr unsigned long long operator()(_Ty _Value) { + if constexpr (is_integral_v<_Ty>) { + if constexpr (is_signed_v<_Ty>) { + if (_Value < 0) { + throw format_error("Negative width."); + } } + return static_cast(_Value); + } else { + throw format_error("Width is not an integer."); } + } +}; - if (_Next_arg_id > 0) { - throw format_error("Can not switch from automatic to manual indexing"); +// Checks that the type and value of an argument associated with a dynamic +// precision specifier are valid. +class _Precision_checker { +public: + template + constexpr unsigned long long operator()(_Ty _Value) { + if constexpr (is_integral_v<_Ty>) { + if constexpr (is_signed_v<_Ty>) { + if (_Value < 0) { + throw format_error("Negative precision."); + } + } + return static_cast(_Value); + } else { + throw format_error("Precision is not an integer."); } - _Next_arg_id = -1; + } +}; + +// Parses standard format specs into a _Basic_format_specs using _Specs_setter, and, +// in addition handles dynamic width and precision. This is separate from _Specs setter +// because it needs to know about the current basic_format_parse_context and basic_format_context +// in order to fetch the width from the arguments. +template +class _Specs_handler : public _Specs_setter { +public: + using _CharT = typename _Context::char_type; + + constexpr _Specs_handler(_Basic_format_specs<_CharT>& _Specs_, _ParseContext& _Parse_ctx_, _Context& _Ctx_) + : _Specs_setter<_CharT>(_Specs_), _Parse_ctx(_Parse_ctx_), _Ctx(_Ctx_) {} + + template + constexpr void _On_dynamic_width(_Id _Arg_id) { + this->_Specs._Width = _Get_dynamic_specs<_Width_checker>(_Get_arg(_Arg_id)); + } + + template + constexpr void _On_dynamic_precision(_Id _Arg_id) { + this->_Specs._Precision = _Get_dynamic_specs<_Precision_checker>(_Get_arg(_Arg_id)); } private: - basic_string_view<_CharT> _Format_string; - size_t _Num_args; - // The standard says this is size_t, however we use ptrdiff_t to save some space - // by not having to store the indexing mode. Above is a more detailed explanation - // of how this works. - ptrdiff_t _Next_arg_id = 0; -}; + _ParseContext& _Parse_ctx; + _Context& _Ctx; -enum class _Basic_format_arg_type : uint8_t { - _None, - _Int_type, - _UInt_type, - _Long_long_type, - _ULong_long_type, - _Bool_type, - _Char_type, - _Float_type, - _Double_type, - _Long_double_type, - _Pointer_type, - _CString_type, - _String_type, - _Custom_type, + constexpr basic_format_arg<_Context> _Get_arg(_Auto_id_tag) { + return _STD _Get_arg(_Ctx, _Parse_ctx.next_arg_id()); + } + + constexpr basic_format_arg<_Context> _Get_arg(size_t _Arg_id) { + _Parse_ctx.check_arg_id(_Arg_id); + return _STD _Get_arg(_Ctx, _Arg_id); + } + + // Fetch the value of an argument associated with a dynamic + // width or precision specifier. This will be called with either + // _Width_checker or _Precision_checker as "_Handler". + template + static constexpr unsigned int _Get_dynamic_specs(_FormatArg _Arg) { + unsigned long long _Val = _STD visit_format_arg(_Handler(), _Arg); + if (_Val > (numeric_limits::max)()) { + throw format_error("Number is too big."); + } + return static_cast(_Val); + } }; -static_assert(static_cast(_Basic_format_arg_type::_Custom_type) <= 16); -template -class basic_format_arg { +class _Numeric_specs_checker { +private: + _Basic_format_arg_type _Arg_type = _Basic_format_arg_type::_None; + public: - using _CharType = typename _Context::char_type; + constexpr explicit _Numeric_specs_checker(_Basic_format_arg_type _Arg_type_) : _Arg_type(_Arg_type_) {} - class handle { - private: - const void* _Ptr; - void (*_Format)(basic_format_parse_context<_CharType>& _Parse_ctx, _Context _Format_ctx, const void*); - friend basic_format_arg; + constexpr void _Require_numeric_argument() const { + if (!_Is_arithmetic_fmt_type(_Arg_type)) { + throw format_error("Format specifier requires numeric argument."); + } + } - public: - template - explicit handle(const _Ty& _Val) noexcept - : _Ptr(_STD addressof(_Val)), - _Format([](basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx, const void* _Ptr) { - typename _Context::template formatter_type<_Ty> _Formatter; - _Parse_ctx.advance_to(_Formatter.parse(_Parse_ctx)); - _Format_ctx.advance_to(_Formatter.format(*static_cast(_Ptr), _Format_ctx)); - }) {} + constexpr void _Check_sign() const { + _Require_numeric_argument(); + if (_Is_integral_fmt_type(_Arg_type) && _Arg_type != _Basic_format_arg_type::_Int_type + && _Arg_type != _Basic_format_arg_type::_Long_long_type + && _Arg_type != _Basic_format_arg_type::_Char_type) { + throw format_error("Format specifier requires signed argument."); + } + } - void format(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx) { - _Format(_Parse_ctx, _Format_ctx, _Ptr); + 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."); } - }; + } +}; - // TRANSITION, LLVM-49072 - basic_format_arg() noexcept : _Active_state(_Basic_format_arg_type::_None), _No_state() {} +// Uses _Numeric_specs_checker to check that the type of the argument printed by +// a replacement field with format specs actually satisfies the requirements for +// that format spec. If the requirements are met then calls the base class +// handler method. +template +class _Specs_checker : public _Handler { +private: + _Numeric_specs_checker _Numeric_checker; - explicit basic_format_arg(const int _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Int_type), _Int_state(_Val) {} - explicit basic_format_arg(const unsigned int _Val) noexcept - : _Active_state(_Basic_format_arg_type::_UInt_type), _UInt_state(_Val) {} - explicit basic_format_arg(const long long _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Long_long_type), _Long_long_state(_Val) {} - explicit basic_format_arg(const unsigned long long _Val) noexcept - : _Active_state(_Basic_format_arg_type::_ULong_long_type), _ULong_long_state(_Val) {} - explicit basic_format_arg(const bool _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Bool_type), _Bool_state(_Val) {} - explicit basic_format_arg(const _CharType _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Char_type), _Char_state(_Val) {} - explicit basic_format_arg(const float _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Float_type), _Float_state(_Val) {} - explicit basic_format_arg(const double _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Double_type), _Double_state(_Val) {} - explicit basic_format_arg(const long double _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Long_double_type), _Long_double_state(_Val) {} - explicit basic_format_arg(const void* _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Pointer_type), _Pointer_state(_Val) {} - explicit basic_format_arg(const _CharType* _Val) noexcept - : _Active_state(_Basic_format_arg_type::_CString_type), _CString_state(_Val) {} - explicit basic_format_arg(const basic_string_view<_CharType> _Val) noexcept - : _Active_state(_Basic_format_arg_type::_String_type), _String_state(_Val) {} - explicit basic_format_arg(const handle _Val) noexcept - : _Active_state(_Basic_format_arg_type::_Custom_type), _Custom_state(_Val) {} - explicit operator bool() const noexcept { - return _Active_state != _Basic_format_arg_type::_None; +public: + constexpr explicit _Specs_checker(const _Handler& _Handler_inst, _Basic_format_arg_type _Arg_type_) + : _Handler(_Handler_inst), _Numeric_checker(_Arg_type_) {} + + // _On_align has no checking, since we don't implement numeric alignments. + + constexpr void _On_sign(_Sign _Sgn) { + _Numeric_checker._Check_sign(); + _Handler::_On_sign(_Sgn); } - _Basic_format_arg_type _Active_state = _Basic_format_arg_type::_None; - union { - monostate _No_state = monostate{}; - int _Int_state; - unsigned int _UInt_state; - long long _Long_long_state; - unsigned long long _ULong_long_state; - bool _Bool_state; - _CharType _Char_state; - float _Float_state; - double _Double_state; - long double _Long_double_state; - const void* _Pointer_state; - const _CharType* _CString_state; - basic_string_view<_CharType> _String_state; - handle _Custom_state; - }; -}; + constexpr void _On_hash() { + // Note that '#' is not valid for CharT or bool unless you + // pass a numeric presentation type, but we encounter '#' before + // the presentation type so we can not check that requirement here + _Numeric_checker._Require_numeric_argument(); + _Handler::_On_hash(); + } -template -auto visit_format_arg(_Visitor&& _Vis, basic_format_arg<_Context> _Arg) { - switch (_Arg._Active_state) { - case _Basic_format_arg_type::_None: - return _Vis(_Arg._No_state); - case _Basic_format_arg_type::_Int_type: - return _Vis(_Arg._Int_state); - case _Basic_format_arg_type::_UInt_type: - return _Vis(_Arg._UInt_state); - case _Basic_format_arg_type::_Long_long_type: - return _Vis(_Arg._Long_long_state); - case _Basic_format_arg_type::_ULong_long_type: - return _Vis(_Arg._ULong_long_state); - case _Basic_format_arg_type::_Bool_type: - return _Vis(_Arg._Bool_state); - case _Basic_format_arg_type::_Char_type: - return _Vis(_Arg._Char_state); - case _Basic_format_arg_type::_Float_type: - return _Vis(_Arg._Float_state); - case _Basic_format_arg_type::_Double_type: - return _Vis(_Arg._Double_state); - case _Basic_format_arg_type::_Long_double_type: - return _Vis(_Arg._Long_double_state); - case _Basic_format_arg_type::_Pointer_type: - return _Vis(_Arg._Pointer_state); - case _Basic_format_arg_type::_CString_type: - return _Vis(_Arg._CString_state); - case _Basic_format_arg_type::_String_type: - return _Vis(_Arg._String_state); - case _Basic_format_arg_type::_Custom_type: - return _Vis(_Arg._Custom_state); - default: - _STL_VERIFY(false, "basic_format_arg is in impossible state"); - return _Vis(0); + constexpr void _On_zero() { + // Note 0 is again not valid for CharT or bool unless a numeric + // presentation type is uesd. + _Numeric_checker._Require_numeric_argument(); + _Handler::_On_zero(); } -} + + constexpr void _On_precision(unsigned int _Precision) { + _Numeric_checker._Check_precision(); + _Handler::_On_precision(_Precision); + } +}; template /* consteval */ constexpr auto _Get_format_arg_storage_type() noexcept { @@ -745,6 +1085,10 @@ public: template using formatter_type = formatter<_Ty, _CharT>; + constexpr basic_format_context( + _Out _OutputIt_, basic_format_args _Ctx_args, const locale& _Loc_) + : _OutputIt(_OutputIt_), _Args(_Ctx_args), _Loc(_Loc_) {} + basic_format_arg arg(size_t _Id) const { return _Args.get(_Id); } @@ -759,6 +1103,121 @@ public: // TODO: IDL support probably required _OutputIt = _It; } + + const basic_format_args& _Get_args() const { + return _Args; + } +}; + +template +_OutputIt _Write(_OutputIt _Out, monostate) { + _STL_INTERNAL_CHECK(false); + return _Out; +} + +template +_OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { + if (!_Value) { + throw format_error("String pointer is null."); + } + while (*_Value) { + *_Out++ = *_Value++; + } + return _Out; +} + +template +_OutputIt _Write(_OutputIt _Out, _Ty _Val) { + (void) _Val; + _STL_INTERNAL_CHECK(false); + return _Out; +} + +// Dispatcher to call enabled custom formatters, and do nothing otherwise. +template +class _Custom_formatter_dispatcher { +private: + using _Char_type = typename _Context::char_type; + + basic_format_parse_context<_Char_type>& _Parse_ctx; + _Context& _Ctx; + +public: + explicit constexpr _Custom_formatter_dispatcher( + basic_format_parse_context<_Char_type>& _Parse_ctx_, _Context& _Ctx_) + : _Parse_ctx(_Parse_ctx_), _Ctx(_Ctx_) {} + + void operator()(typename basic_format_arg<_Context>::handle _Handle) const { + _Handle.format(_Parse_ctx, _Ctx); + } + + template + void operator()(_Ty) const {} +}; + +// This is the visitor that's used for "simple" replacement fields, +// it could be a generic lambda (with overloaded), but that's +// bad for throughput. A simple replacement field is a replacement field +// that's just "{}", without any format specs. +template +struct _Default_arg_formatter { + using _Context = basic_format_context<_OutputIt, _CharT>; + + _OutputIt _Out; + basic_format_args<_Context> _Args; + locale _Loc; + + template + _OutputIt operator()(_Ty _Val) { + return _Write<_CharT>(_Out, _Val); + } + + _OutputIt operator()(typename basic_format_arg<_Context>::handle _Handle) { + basic_format_parse_context<_CharT> _Parse_ctx({}); + basic_format_context<_OutputIt, _CharT> _Format_ctx(_Out, _Args, _Loc); + _Handle.format(_Parse_ctx, _Format_ctx); + return _Format_ctx.out(); + } +}; + +// The top level set of parsing "actions". +template +struct _Format_handler { + basic_format_parse_context<_CharT> _Parse_context; + _Context _Ctx; + + explicit _Format_handler( + _OutputIt _Out, basic_string_view<_CharT> _Str, basic_format_args<_Context> _Format_args, const locale& _Loc) + : _Parse_context(_Str), _Ctx(_Out, _Format_args, _Loc) {} + + void _On_text(const _CharT* _Begin, const _CharT* _End) { + auto _Size = _End - _Begin; + auto _Out = _Ctx.out(); + _Out = _STD copy_n(_Begin, _Size, _Out); + _Ctx.advance_to(_Out); + } + + void _On_replacement_field(size_t _Id, const _CharT*) { + auto _Arg = _Ctx.arg(_Id); + _Ctx.advance_to(visit_format_arg( + _Default_arg_formatter<_OutputIt, _CharT>{_Ctx.out(), _Ctx._Get_args(), _Ctx.locale()}, _Arg)); + } + + const _CharT* _On_format_specs(size_t _Id, const _CharT* _Begin, const _CharT* _End) { + _Parse_context.advance_to(_Parse_context.begin() + (_Begin - &*_Parse_context.begin())); + auto _Arg = _Ctx.arg(_Id); + _Basic_format_specs<_CharT> _Specs; + _Specs_checker<_Specs_handler, _Context>> _Handler( + _Specs_handler, _Context>(_Specs, _Parse_context, _Ctx), + _Arg._Active_state); + _Begin = _Parse_format_specs(_Begin, _End, _Handler); + if (_Begin == _End || *_Begin != '}') { + throw format_error("Missing '}' in format string."); + } + // TODO: implement format spec dispatching. + _STL_INTERNAL_CHECK(false); + return _Begin; + } }; using format_context = basic_format_context, string::value_type>; @@ -776,6 +1235,17 @@ auto make_wformat_args(const _Args&... _Vals) { return _Format_arg_store<_Context, _Args...>{_Vals...}; } +template +using format_args_t = basic_format_args>; + +template +_Out vformat_to( + _Out _OutputIt, const locale& _Loc, string_view _Fmt, format_args_t, char> _Args) { + _Format_handler<_Out, char, basic_format_context<_Out, char>> _Handler(_OutputIt, _Fmt, _Args, _Loc); + _Parse_format_string(_Fmt, _Handler); + return _Handler._Ctx.out(); +} + // FUNCTION vformat string vformat(string_view _Fmt, format_args _Args); wstring vformat(wstring_view _Fmt, wformat_args _Args); diff --git a/tests/std/test.lst b/tests/std/test.lst index e179bf9671e..d71e3493b8d 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -255,6 +255,7 @@ tests\P0616R0_using_move_in_numeric tests\P0631R8_numbers_math_constants tests\P0645R10_text_formatting_args tests\P0645R10_text_formatting_death +tests\P0645R10_text_formatting_formatting tests\P0645R10_text_formatting_parse_contexts tests\P0645R10_text_formatting_parsing tests\P0660R10_jthread_and_cv_any diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/env.lst b/tests/std/tests/P0645R10_text_formatting_formatting/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_formatting/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp new file mode 100644 index 00000000000..3c0f0d9173d --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +// TODO: fill in tests +template std::back_insert_iterator std::vformat_to(std::back_insert_iterator, + const std::locale&, std::string_view, std::format_args_t, char>); + + +int main() {} diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index 85da914a9ac..9340bf3f3fa 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -30,16 +30,32 @@ struct choose_literal { #define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) +template +struct noop_testing_callbacks { + constexpr void _On_align(_Align) {} + constexpr void _On_fill(basic_string_view) {} + constexpr void _On_width(unsigned int) {} + constexpr void _On_dynamic_width(size_t) {} + constexpr void _On_dynamic_width(_Auto_id_tag) {} + constexpr void _On_precision(unsigned int) {} + constexpr void _On_dynamic_precision(size_t) {} + constexpr void _On_dynamic_precision(_Auto_id_tag) {} + constexpr void _On_sign(_Sign) {} + constexpr void _On_hash() {} + constexpr void _On_zero() {} + constexpr void _On_type(CharT) {} +}; + template struct testing_callbacks { _Align expected_alignment = _Align::_None; _Sign expected_sign = _Sign::_None; basic_string_view expected_fill; - int expected_width = -1; - int expected_dynamic_width = -1; + unsigned int expected_width = static_cast(-1); + size_t expected_dynamic_width = static_cast(-1); bool expected_auto_dynamic_width = false; - int expected_precision = -1; - int expected_dynamic_precision = -1; + unsigned int expected_precision = static_cast(-1); + size_t expected_dynamic_precision = static_cast(-1); bool expected_auto_dynamic_precision = false; bool expected_hash = false; bool expected_zero = false; @@ -50,19 +66,19 @@ struct testing_callbacks { constexpr void _On_fill(basic_string_view str_view) { assert(str_view == expected_fill); } - constexpr void _On_width(int width) { + constexpr void _On_width(unsigned int width) { assert(width == expected_width); } - constexpr void _On_dynamic_width(int id) { + constexpr void _On_dynamic_width(size_t id) { assert(id == expected_dynamic_width); } constexpr void _On_dynamic_width(_Auto_id_tag) { assert(expected_auto_dynamic_width); } - constexpr void _On_precision(int pre) { + constexpr void _On_precision(unsigned int pre) { assert(pre == expected_precision); } - constexpr void _On_dynamic_precision(int id) { + constexpr void _On_dynamic_precision(size_t id) { assert(id == expected_dynamic_precision); } constexpr void _On_dynamic_precision(_Auto_id_tag) { @@ -86,7 +102,7 @@ testing_callbacks(_Align, basic_string_view) -> testing_callbacks; struct testing_arg_id_callbacks { constexpr void _On_auto_id() {} - constexpr void _On_manual_id(int) {} + constexpr void _On_manual_id(size_t) {} }; template @@ -262,6 +278,24 @@ constexpr bool test_parse_format_specs() { return true; } +template +constexpr bool test_specs_setter() { + // just instantiate for now. + _Basic_format_specs specs = {}; + _Specs_setter setter(specs); + + (void) setter; + return true; +} + +template +constexpr bool test_specs_checker() { + _Specs_checker> checker( + noop_testing_callbacks{}, _Basic_format_arg_type::_Float_type); + (void) checker; + return true; +} + int main() { test_parse_align(); test_parse_align(); @@ -288,5 +322,15 @@ int main() { static_assert(test_parse_format_specs()); static_assert(test_parse_format_specs()); + test_specs_setter(); + test_specs_setter(); + static_assert(test_specs_setter()); + static_assert(test_specs_setter()); + + test_specs_checker(); + test_specs_checker(); + static_assert(test_specs_checker()); + static_assert(test_specs_checker()); + return 0; } From 99e8a2d7f2a083a6f768cff35a5e1792feb625a6 Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Fri, 19 Feb 2021 12:59:56 -0800 Subject: [PATCH 09/94] : vformat_to parsing and escaping tests (#1661) * add tests for various escaped curlies and simple text. * better comment in _Parse_format_string Co-authored-by: Casey Carter --- stl/inc/format | 7 +- .../test.cpp | 69 ++++++++++++++++++- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 65b9145847a..bf09d0572f2 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -579,14 +579,15 @@ constexpr void _Parse_format_string(basic_string_view<_CharT> _Format_str, _Hand const _CharT* _OpeningCurl = _Begin; if (*_Begin != '{') { // we didn't start at an opening curl, find the next one - _OpeningCurl = _STD find(_Begin + 1, _End, '{'); + _OpeningCurl = _Find_unchecked(_Begin + 1, _End, '{'); for (;;) { const _CharT* _ClosingCurl = _Find_unchecked(_Begin, _OpeningCurl, '}'); - // In this case we didn't find either a closing curl or opening curl. + // In this case there are neither closing nor opening curls in [_Begin, _OpenCurl) // Write the whole thing out. if (_ClosingCurl == _OpeningCurl) { - return _Handler._On_text(_Begin, _OpeningCurl); + _Handler._On_text(_Begin, _OpeningCurl); + break; } // We know _ClosingCurl isn't past the end because // the above condition was not met. diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 3c0f0d9173d..1c97b8de8ae 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -8,9 +8,72 @@ #include #include +using namespace std; + // TODO: fill in tests -template std::back_insert_iterator std::vformat_to(std::back_insert_iterator, - const std::locale&, std::string_view, std::format_args_t, char>); +template back_insert_iterator std::vformat_to( + back_insert_iterator, const locale&, string_view, format_args_t, char>); + + +int main() { + string output_string = ""; + + vformat_to(back_insert_iterator(output_string), locale::classic(), "f", make_format_args()); + assert(output_string == "f"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "format", make_format_args()); + assert(output_string == "format"); + + // test escaped opening curls + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{{", make_format_args()); + assert(output_string == "{"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{{{{", make_format_args()); + assert(output_string == "{{"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "x{{", make_format_args()); + assert(output_string == "x{"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{{ {{", make_format_args()); + assert(output_string == "{ {"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "x{{x", make_format_args()); + assert(output_string == "x{x"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{{x", make_format_args()); + assert(output_string == "{x"); + + // tests escaped closing curls + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "}}", make_format_args()); + assert(output_string == "}"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "}}}}", make_format_args()); + assert(output_string == "}}"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "x}}", make_format_args()); + assert(output_string == "x}"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "}} }}", make_format_args()); + assert(output_string == "} }"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "x}}x", make_format_args()); + assert(output_string == "x}x"); + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "}}x", make_format_args()); + assert(output_string == "}x"); -int main() {} + return 0; +} From 62e9bbea0d0f5cfc9401c2be9b73e0c5045fac7e Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Wed, 24 Feb 2021 11:14:32 -0800 Subject: [PATCH 10/94] : initial replacement field tests (and numeric arg indexing) (#1675) * intial replacement field tests (and numeric arg indexing) --- .../test.cpp | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 1c97b8de8ae..662d331fa6f 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -9,7 +9,6 @@ #include using namespace std; - // TODO: fill in tests template back_insert_iterator std::vformat_to( back_insert_iterator, const locale&, string_view, format_args_t, char>); @@ -75,5 +74,26 @@ int main() { vformat_to(back_insert_iterator(output_string), locale::classic(), "}}x", make_format_args()); assert(output_string == "}x"); + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args((const char*) "f")); + assert(output_string == "f"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{0} {0}", make_format_args((const char*) "f")); + assert(output_string == "f f"); + + // TODO: enable these in _Write function PR for sv and bool + /* + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args("f"sv)); + assert(output_string == "f"); + */ + + /* + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(true)); + assert(output_string == "true"); + */ + return 0; } From 9213d3585832a1aa241c1098ae3cec0f2f9c920b Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Thu, 25 Feb 2021 21:50:34 -0800 Subject: [PATCH 11/94] : Fix _Get_format_arg_storage_type (#1696) * Use proper overload set for _Get_format_arg_storage_size * try and fix bugs in miscco's approach * fix compile errors and return to the old method of first getting the type * add _Has_formatter * add missing context parameter * fix nullptr_t overload * Apply suggestions from code review Co-authored-by: Stephan T. Lavavej Co-authored-by: Michael Schellenberger Costa Co-authored-by: Stephan T. Lavavej --- stl/inc/format | 77 +++++++++++++++---- .../test.cpp | 4 +- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index bf09d0572f2..ac87f0bd84b 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -840,15 +840,36 @@ public: } }; +// clang-format off template -/* consteval */ constexpr auto _Get_format_arg_storage_type() noexcept { - using _CharType = typename _Context::char_type; +concept _Has_formatter = requires(const _Ty& _Val, _Context& _Ctx) { + _STD declval>().format(_Val, _Ctx); +}; +// clang-format on + +// See N4878 [format.arg]/5 +// clang-format off +template + requires _Has_formatter<_Context, _Ty> +/* consteval */ constexpr auto _Get_format_arg_storage_type(const _Ty& _Val) noexcept { + // clang-format on + return typename basic_format_arg<_Context>::handle{_Val}; +} + +// clang-format off +template + requires integral<_Ty> || floating_point<_Ty> +/* consteval */ constexpr auto _Get_format_arg_storage_type(_Ty) noexcept { + // clang-format on + using _Char_type = typename _Context::char_type; if constexpr (is_same_v<_Ty, monostate>) { return monostate{}; - } else if constexpr (is_same_v<_Ty, _CharType>) { - return _CharType{}; - } else if constexpr (is_same_v<_Ty, char> && is_same_v<_CharType, wchar_t>) { - return _CharType{}; + } else if constexpr (is_same_v<_Ty, bool>) { + return bool{}; + } else if constexpr (is_same_v<_Ty, _Char_type>) { + return _Char_type{}; + } else if constexpr (is_same_v<_Ty, char> && is_same_v<_Char_type, wchar_t>) { + return _Char_type{}; } else if constexpr (signed_integral<_Ty> && sizeof(_Ty) <= sizeof(int)) { return int{}; } else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned int)) { @@ -857,27 +878,49 @@ template return static_cast(42); } else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned long long)) { return static_cast(42); - } else if constexpr (is_same_v<_Ty, bool>) { - return bool{}; } else if constexpr (is_same_v<_Ty, float>) { return float{}; } else if constexpr (is_same_v<_Ty, double>) { return double{}; } else if constexpr (is_same_v<_Ty, long double>) { return static_cast(42); - } else if constexpr (is_same_v<_Ty, const void*>) { - return static_cast(nullptr); - } else if constexpr (is_same_v<_Ty, const _CharType*>) { - return static_cast(nullptr); - } else if constexpr (is_same_v<_Ty, basic_string_view<_CharType>>) { - return basic_string_view<_CharType>{}; } else { - return basic_format_arg<_Context>::handle(); + static_assert(_Always_false<_Ty>, "Invalid type passed to _Get_format_arg_storage_type"); + return static_cast(-1); } } +template +/* consteval */ constexpr auto _Get_format_arg_storage_type(const _Char_type*) noexcept { + return static_cast(nullptr); +} + +template +/* consteval */ constexpr auto _Get_format_arg_storage_type(basic_string_view<_Char_type, _Traits>) noexcept { + return basic_string_view<_Char_type>{}; +} + +template +/* consteval */ constexpr auto _Get_format_arg_storage_type(const basic_string<_Char_type, _Traits, _Alloc>&) noexcept { + return basic_string_view<_Char_type>{}; +} + +template +/* consteval */ constexpr auto _Get_format_arg_storage_type(nullptr_t) noexcept { + return static_cast(nullptr); +} + +// clang-format off +template + requires is_void_v<_Ty> +/* consteval */ constexpr auto _Get_format_arg_storage_type(const _Ty*) noexcept { + // clang-format on + return static_cast(nullptr); +} + template -inline constexpr size_t _Get_format_arg_storage_size = sizeof(_Get_format_arg_storage_type<_Context, _Ty>()); +inline constexpr size_t _Get_format_arg_storage_size = sizeof( + _Get_format_arg_storage_type<_Context>(_STD declval<_Ty>())); struct _Format_arg_store_packed_index { // TRANSITION, Should be templated on number of arguments for even less storage @@ -913,7 +956,7 @@ private: void _Store_impl(const size_t _Arg_index, const _Basic_format_arg_type _Arg_type, _Ty _Val) noexcept { const auto _Index_array = reinterpret_cast<_Index_type*>(_Storage); const auto _Store_index = _Index_array[_Arg_index]._Index; - const auto _Length = _Get_format_arg_storage_size<_Context, _Ty>; + const auto _Length = sizeof(_Get_format_arg_storage_type<_Context>(_Val)); _CSTD memcpy(_Storage + _Index_length + _Store_index, _STD addressof(_Val), _Length); _Index_array[_Arg_index]._Type = _Arg_type; diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 662d331fa6f..8143493d6a6 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -75,11 +75,11 @@ int main() { assert(output_string == "}x"); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args((const char*) "f")); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args("f")); assert(output_string == "f"); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{0} {0}", make_format_args((const char*) "f")); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{0} {0}", make_format_args("f")); assert(output_string == "f f"); // TODO: enable these in _Write function PR for sv and bool From ad71e87456e2269c6a7cd12585f4a108a106fc88 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Mon, 1 Mar 2021 11:11:06 -0800 Subject: [PATCH 12/94] : add no-spec formatting (#1695) * : add no-spec formatting * add _Write functions for all the possible values of args * add wide v_format (untested) * fix cast of spec.type to cast to char * Update stl/inc/format Co-authored-by: Casey Carter * Address PR comments * Undo some changes, more tests * remove -nan, basically just testing to_chars at that point Co-authored-by: Casey Carter --- stl/inc/format | 65 ++++++- .../test.cpp | 170 +++++++++++++++++- 2 files changed, 222 insertions(+), 13 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index ac87f0bd84b..46c1f1de0a6 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1159,6 +1159,47 @@ _OutputIt _Write(_OutputIt _Out, monostate) { return _Out; } +// This size is derived from the maximum length of an arithmetic type. The two contenders for widest are double and long +// long. long long has a max length of ceil(log_10(2^64)) = 20 characters. double has a max length of +// limits::max_digits10 + the decimal + the sign + e + the exponent's sign + ceil(log_10(DBL_MAX_10_EXP)) +// = 17 + 1 + 1 + 1 + 3 = 24. An example is DBL_MAX which is "-1.7976931348623158e+308". +inline constexpr size_t _Format_min_buffer_length = 24; + +// clang-format off +template + requires (is_arithmetic_v<_Arithmetic> && !same_as<_Arithmetic, bool> && !same_as<_Arithmetic, _CharT>) +_OutputIt _Write(_OutputIt _Out, _Arithmetic _Value) { + // clang-format on + // TRANSITION, Reusable buffer + array _Buffer; + const auto [_End, _Ec] = _STD to_chars(_Buffer.data(), _Buffer.data() + _Buffer.size(), _Value); + _STL_ASSERT(_Ec == errc{}, "to_chars failed"); + return _STD copy(_Buffer.data(), _End, _Out); +} + +template +_OutputIt _Write(_OutputIt _Out, bool _Value) { + return _Write(_Out, _Value ? "true" : "false"); +} + +template +_OutputIt _Write(_OutputIt _Out, _CharT _Value) { + *_Out = _Value; + return ++_Out; +} + +template +_OutputIt _Write(_OutputIt _Out, const void* _Value) { + // TRANSITION, Reusable buffer + array _Buffer; + const auto [_End, _Ec] = + _STD to_chars(_Buffer.data(), _Buffer.data() + _Buffer.size(), reinterpret_cast(_Value), 16); + _STL_ASSERT(_Ec == errc{}, "to_chars failed"); + *_Out++ = '0'; + *_Out++ = 'x'; + return _STD copy(_Buffer.data(), _End, _Out); +} + template _OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { if (!_Value) { @@ -1170,11 +1211,9 @@ _OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { return _Out; } -template -_OutputIt _Write(_OutputIt _Out, _Ty _Val) { - (void) _Val; - _STL_INTERNAL_CHECK(false); - return _Out; +template +_OutputIt _Write(_OutputIt _Out, basic_string_view<_CharT> _Value) { + return _STD copy(_Value.begin(), _Value.end(), _Out); } // Dispatcher to call enabled custom formatters, and do nothing otherwise. @@ -1282,10 +1321,18 @@ auto make_wformat_args(const _Args&... _Vals) { template using format_args_t = basic_format_args>; -template -_Out vformat_to( - _Out _OutputIt, const locale& _Loc, string_view _Fmt, format_args_t, char> _Args) { - _Format_handler<_Out, char, basic_format_context<_Out, char>> _Handler(_OutputIt, _Fmt, _Args, _Loc); +template +_OutputIt vformat_to( + _OutputIt _Out, const locale& _Loc, string_view _Fmt, format_args_t, char> _Args) { + _Format_handler<_OutputIt, char, basic_format_context<_OutputIt, char>> _Handler(_Out, _Fmt, _Args, _Loc); + _Parse_format_string(_Fmt, _Handler); + return _Handler._Ctx.out(); +} + +template +_OutputIt vformat_to( + _OutputIt _Out, const locale& _Loc, wstring_view _Fmt, format_args_t, wchar_t> _Args) { + _Format_handler<_OutputIt, wchar_t, basic_format_context<_OutputIt, wchar_t>> _Handler(_Out, _Fmt, _Args, _Loc); _Parse_format_string(_Fmt, _Handler); return _Handler._Ctx.out(); } diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 8143493d6a6..3ed23a064ff 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -82,18 +82,180 @@ int main() { vformat_to(back_insert_iterator(output_string), locale::classic(), "{0} {0}", make_format_args("f")); assert(output_string == "f f"); - // TODO: enable these in _Write function PR for sv and bool - /* + // Test string_view output_string.clear(); vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args("f"sv)); assert(output_string == "f"); - */ - /* + // Test bool output_string.clear(); vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(true)); assert(output_string == "true"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(false)); + assert(output_string == "false"); + + // Test char + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args('a')); + assert(output_string == "a"); + + // Test const void* + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", + make_format_args(static_cast(nullptr))); + assert(output_string == "0x0"); + + /* TODO: Doesn't properly overload on void* and nullptr + // Test void* + output_string.clear(); + vformat_to( + back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(static_cast(nullptr))); + assert(output_string == "0x0"); + + // Test nullptr + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(nullptr)); + assert(output_string == "0x0"); */ + // Test signed integers + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(1234)); + assert(output_string == "1234"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(1234ll)); + assert(output_string == "1234"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(INT_MIN)); + assert(output_string == "-2147483648"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(INT_MAX)); + assert(output_string == "2147483647"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(LLONG_MAX)); + assert(output_string == "9223372036854775807"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(LLONG_MIN)); + assert(output_string == "-9223372036854775808"); + + // Test unsigned integers + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(1234u)); + assert(output_string == "1234"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(1234ull)); + assert(output_string == "1234"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(UINT_MAX)); + assert(output_string == "4294967295"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(ULLONG_MAX)); + assert(output_string == "18446744073709551615"); + + // Test float + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(12.34f)); + assert(output_string == "12.34"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(FLT_MAX)); + assert(output_string == "3.4028235e+38"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(-FLT_MAX)); + assert(output_string == "-3.4028235e+38"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(FLT_MIN)); + assert(output_string == "1.1754944e-38"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(FLT_EPSILON)); + assert(output_string == "1.1920929e-07"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(FLT_TRUE_MIN)); + assert(output_string == "1e-45"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", + make_format_args(numeric_limits::infinity())); + assert(output_string == "inf"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", + make_format_args(-numeric_limits::infinity())); + assert(output_string == "-inf"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", + make_format_args(numeric_limits::quiet_NaN())); + assert(output_string == "nan"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(0.f)); + assert(output_string == "0"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(-0.f)); + assert(output_string == "-0"); + + // Test double + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(12.34)); + assert(output_string == "12.34"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(DBL_MAX)); + assert(output_string == "1.7976931348623157e+308"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(-DBL_MAX)); + assert(output_string == "-1.7976931348623157e+308"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(DBL_MIN)); + assert(output_string == "2.2250738585072014e-308"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(DBL_EPSILON)); + assert(output_string == "2.220446049250313e-16"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(DBL_TRUE_MIN)); + assert(output_string == "5e-324"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", + make_format_args(numeric_limits::infinity())); + assert(output_string == "inf"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", + make_format_args(-numeric_limits::infinity())); + assert(output_string == "-inf"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", + make_format_args(numeric_limits::quiet_NaN())); + assert(output_string == "nan"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(0.0)); + assert(output_string == "0"); + + output_string.clear(); + vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(-0.0)); + assert(output_string == "-0"); return 0; } From f861ff0000b125862c44ed65afdbbd2825235256 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Mon, 1 Mar 2021 17:44:20 -0800 Subject: [PATCH 13/94] : Enable void* and nullptr tests (#1705) Enables the tests, fixes a bug that prevents the tests from working by constraining `_Store` on `_Has_formatter`. Also fixes a (visual only) inconsistency of the return type of `_Get_format_arg_store_type`. --- stl/inc/format | 5 ++++- tests/std/tests/P0645R10_text_formatting_formatting/test.cpp | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 46c1f1de0a6..ec97153edc9 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -915,7 +915,7 @@ template requires is_void_v<_Ty> /* consteval */ constexpr auto _Get_format_arg_storage_type(const _Ty*) noexcept { // clang-format on - return static_cast(nullptr); + return static_cast(nullptr); } template @@ -967,8 +967,11 @@ private: } // See [format.arg]/5 + // clang-format off template + requires _Has_formatter<_Context, _Ty> void _Store(const size_t _Arg_index, const _Ty& _Val) noexcept { + // clang-format on _Store_impl::handle>( _Arg_index, _Basic_format_arg_type::_Custom_type, basic_format_arg<_Context>::handle(_Val)); } diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 3ed23a064ff..87bcc9ab9cf 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -107,7 +107,6 @@ int main() { make_format_args(static_cast(nullptr))); assert(output_string == "0x0"); - /* TODO: Doesn't properly overload on void* and nullptr // Test void* output_string.clear(); vformat_to( @@ -118,7 +117,6 @@ int main() { output_string.clear(); vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(nullptr)); assert(output_string == "0x0"); - */ // Test signed integers output_string.clear(); From 3a03973c38ee1e89a312b62e0aed3d8d9b20afe1 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 2 Mar 2021 11:03:55 -0800 Subject: [PATCH 14/94] call the specful writer for replacement fields with specs. --- stl/inc/format | 80 +++++++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index ec97153edc9..e767a4036d7 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -615,14 +615,15 @@ constexpr void _Parse_format_string(basic_string_view<_CharT> _Format_str, _Hand template struct _Basic_format_specs { - unsigned int _Width; - unsigned int _Precision; - char _Type; - _Align _Alignment; - _Sign _Sgn; - bool _Alt; + unsigned int _Width = 0; + unsigned int _Precision = 0; + char _Type = '\0'; + _Align _Alignment = _Align::_None; + _Sign _Sgn = _Sign::_None; + bool _Alt = false; + bool _Localized = false; // At most one codepoint (so one char32_t or four utf-8 char8_t). - _CharT _Fill[4]; + _CharT _Fill[4] = {' ', _CharT{0}, _CharT{0}, _CharT{0}}; }; // Model of _Parse_specs_callbacks that fills a _Basic_format_specs with the parsed data. @@ -665,7 +666,7 @@ public: } constexpr void _On_type(_CharT _Type) { - _Specs._Type = static_cast<_CharT>(_Type); + _Specs._Type = static_cast(_Type); } protected: @@ -1219,27 +1220,14 @@ _OutputIt _Write(_OutputIt _Out, basic_string_view<_CharT> _Value) { return _STD copy(_Value.begin(), _Value.end(), _Out); } -// Dispatcher to call enabled custom formatters, and do nothing otherwise. -template -class _Custom_formatter_dispatcher { -private: - using _Char_type = typename _Context::char_type; - - basic_format_parse_context<_Char_type>& _Parse_ctx; - _Context& _Ctx; - -public: - explicit constexpr _Custom_formatter_dispatcher( - basic_format_parse_context<_Char_type>& _Parse_ctx_, _Context& _Ctx_) - : _Parse_ctx(_Parse_ctx_), _Ctx(_Ctx_) {} - - void operator()(typename basic_format_arg<_Context>::handle _Handle) const { - _Handle.format(_Parse_ctx, _Ctx); - } - - template - void operator()(_Ty) const {} -}; +// TODO: placeholder write +template +_OutputIt _Write(_OutputIt _Out, _Ty _Val, _Basic_format_specs<_CharT>& _Specs) { + _STL_INTERNAL_CHECK(false); + (void) _Val; + (void) _Specs; + return _Out; +} // This is the visitor that's used for "simple" replacement fields, // it could be a generic lambda (with overloaded), but that's @@ -1266,6 +1254,31 @@ struct _Default_arg_formatter { } }; +// Visitor used for replacement fields that contain specs +template +struct _Arg_formatter { + using _Context = basic_format_context<_OutputIt, _CharT>; + + _Context* _Ctx = nullptr; + _Basic_format_specs<_CharT>* _Specs = nullptr; + basic_format_parse_context<_CharT>* _Parse_ctx = nullptr; + + _OutputIt operator()(typename basic_format_arg<_Context>::handle _Handle) { + _STL_ASSERT(false, "The custom handler should be structurally unreachable for _Arg_formatter"); + _STL_INTERNAL_CHECK(_Parse_ctx); + _STL_INTERNAL_CHECK(_Ctx); + _Handle.format(*_Parse_ctx, *_Ctx); + return _Ctx->out(); + } + + template + _OutputIt operator()(_Ty _Val) { + _STL_INTERNAL_CHECK(_Specs); + _STL_INTERNAL_CHECK(_Ctx); + return _Write(_Ctx->out(), _Val, *_Specs); + } +}; + // The top level set of parsing "actions". template struct _Format_handler { @@ -1300,8 +1313,13 @@ struct _Format_handler { if (_Begin == _End || *_Begin != '}') { throw format_error("Missing '}' in format string."); } - // TODO: implement format spec dispatching. - _STL_INTERNAL_CHECK(false); + _Ctx.advance_to(visit_format_arg( + _Arg_formatter<_OutputIt, _CharT>{ + ._Ctx = &_Ctx, + ._Specs = &_Specs, + ._Parse_ctx = &_Parse_context, + }, + _Arg)); return _Begin; } }; From a16a5db7f214181ab64c9eeb9ad2055d5aa66375 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 2 Mar 2021 11:51:31 -0800 Subject: [PATCH 15/94] change width and precision to be signed, and add signed overload of _Parse_nonnegative_integer --- stl/inc/format | 50 +++++++++++-------- .../P0645R10_text_formatting_parsing/test.cpp | 8 +-- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index e767a4036d7..0bafdfe8811 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -71,10 +71,10 @@ template concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln, _Sign _Sgn) { { _At._On_align(_Aln) } -> same_as; { _At._On_fill(_Sv) } -> same_as; - { _At._On_width(static_cast(0)) } -> same_as; + { _At._On_width(int{}) } -> same_as; { _At._On_dynamic_width(size_t{}) } -> same_as; { _At._On_dynamic_width(_Auto_id_tag{}) } -> same_as; - { _At._On_precision(static_cast(0)) } -> same_as; + { _At._On_precision(int{}) } -> same_as; { _At._On_dynamic_precision(size_t{}) } -> same_as; { _At._On_dynamic_precision(_Auto_id_tag{}) } -> same_as; { _At._On_sign(_Sgn) } -> same_as; @@ -278,7 +278,7 @@ auto visit_format_arg(_Visitor&& _Vis, basic_format_arg<_Context> _Arg) { } } -// we need to implement this ourselves because from_chars does not work with wide characters +// we need to implement this ourselves because from_chars does not work with wide characters and isn't constexpr template constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _CharT* _End, unsigned int& _Value) { _STL_INTERNAL_CHECK(_Begin != _End && '0' <= *_Begin && *_Begin <= '9'); @@ -300,6 +300,16 @@ constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _ return _Begin; } +template +constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _CharT* _End, int& _Value) { + unsigned int _Val_unsigned = 0; + + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Val_unsigned); + // Never invalid because _Parse_nonnegative_integer throws an error for values that don't fit in signed integers + _Value = static_cast(_Val_unsigned); + return _Begin; +} + template _Callbacks_type> constexpr const _CharT* _Parse_arg_id(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { _STL_INTERNAL_CHECK(_Begin != _End); @@ -432,8 +442,8 @@ template _Callbacks_type> constexpr const _CharT* _Parse_width(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { _STL_INTERNAL_CHECK(_Begin != _End); if ('1' <= *_Begin && *_Begin <= '9') { - unsigned int _Value = 0; - _Begin = _Parse_nonnegative_integer(_Begin, _End, _Value); + int _Value = 0; + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Value); _Callbacks._On_width(_Value); } else if (*_Begin == '{') { ++_Begin; @@ -457,8 +467,8 @@ constexpr const _CharT* _Parse_precision(const _CharT* _Begin, const _CharT* _En } if ('0' <= _Ch && _Ch <= '9') { - unsigned int _Precision = 0; - _Begin = _Parse_nonnegative_integer(_Begin, _End, _Precision); + int _Precision = 0; + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Precision); _Callbacks._On_precision(_Precision); } else if (_Ch == '{') { ++_Begin; @@ -615,13 +625,13 @@ constexpr void _Parse_format_string(basic_string_view<_CharT> _Format_str, _Hand template struct _Basic_format_specs { - unsigned int _Width = 0; - unsigned int _Precision = 0; - char _Type = '\0'; - _Align _Alignment = _Align::_None; - _Sign _Sgn = _Sign::_None; - bool _Alt = false; - bool _Localized = false; + int _Width = 0; + int _Precision = -1; + char _Type = '\0'; + _Align _Alignment = _Align::_None; + _Sign _Sgn = _Sign::_None; + bool _Alt = false; + bool _Localized = false; // At most one codepoint (so one char32_t or four utf-8 char8_t). _CharT _Fill[4] = {' ', _CharT{0}, _CharT{0}, _CharT{0}}; }; @@ -657,11 +667,11 @@ public: _Specs._Fill[0] = _CharT{'0'}; } - constexpr void _On_width(unsigned int _Width) { + constexpr void _On_width(int _Width) { _Specs._Width = _Width; } - constexpr void _On_precision(unsigned int _Precision) { + constexpr void _On_precision(int _Precision) { _Specs._Precision = _Precision; } @@ -762,12 +772,12 @@ private: // width or precision specifier. This will be called with either // _Width_checker or _Precision_checker as "_Handler". template - static constexpr unsigned int _Get_dynamic_specs(_FormatArg _Arg) { + static constexpr int _Get_dynamic_specs(_FormatArg _Arg) { unsigned long long _Val = _STD visit_format_arg(_Handler(), _Arg); - if (_Val > (numeric_limits::max)()) { + if (_Val > (numeric_limits::max)()) { throw format_error("Number is too big."); } - return static_cast(_Val); + return static_cast(_Val); } }; @@ -835,7 +845,7 @@ public: _Handler::_On_zero(); } - constexpr void _On_precision(unsigned int _Precision) { + constexpr void _On_precision(int _Precision) { _Numeric_checker._Check_precision(); _Handler::_On_precision(_Precision); } diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index 9340bf3f3fa..831ff50fab4 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -51,10 +51,10 @@ struct testing_callbacks { _Align expected_alignment = _Align::_None; _Sign expected_sign = _Sign::_None; basic_string_view expected_fill; - unsigned int expected_width = static_cast(-1); + int expected_width = -1; size_t expected_dynamic_width = static_cast(-1); bool expected_auto_dynamic_width = false; - unsigned int expected_precision = static_cast(-1); + int expected_precision = -1; size_t expected_dynamic_precision = static_cast(-1); bool expected_auto_dynamic_precision = false; bool expected_hash = false; @@ -66,7 +66,7 @@ struct testing_callbacks { constexpr void _On_fill(basic_string_view str_view) { assert(str_view == expected_fill); } - constexpr void _On_width(unsigned int width) { + constexpr void _On_width(int width) { assert(width == expected_width); } constexpr void _On_dynamic_width(size_t id) { @@ -75,7 +75,7 @@ struct testing_callbacks { constexpr void _On_dynamic_width(_Auto_id_tag) { assert(expected_auto_dynamic_width); } - constexpr void _On_precision(unsigned int pre) { + constexpr void _On_precision(int pre) { assert(pre == expected_precision); } constexpr void _On_dynamic_precision(size_t id) { From dd4ba28985a7caa49d7a12d4d058a72c50c0e976 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 2 Mar 2021 12:46:07 -0800 Subject: [PATCH 16/94] check type to ensure it's in range. --- stl/inc/format | 3 +++ 1 file changed, 3 insertions(+) diff --git a/stl/inc/format b/stl/inc/format index 0bafdfe8811..bb145691609 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -676,6 +676,9 @@ public: } constexpr void _On_type(_CharT _Type) { + if (_Type < 0 || _Type > (numeric_limits::max)()) { + throw format_error("Invalid type specification."); + } _Specs._Type = static_cast(_Type); } From 90306b55696655e1a74209b42dc51aa75ce4038b Mon Sep 17 00:00:00 2001 From: Elnar D Date: Fri, 5 Mar 2021 14:05:27 -0800 Subject: [PATCH 17/94] Add leading zero --- stl/inc/format | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index bb145691609..572568968f6 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -625,13 +625,14 @@ constexpr void _Parse_format_string(basic_string_view<_CharT> _Format_str, _Hand template struct _Basic_format_specs { - int _Width = 0; - int _Precision = -1; - char _Type = '\0'; - _Align _Alignment = _Align::_None; - _Sign _Sgn = _Sign::_None; - bool _Alt = false; - bool _Localized = false; + int _Width = 0; + int _Precision = -1; + char _Type = '\0'; + _Align _Alignment = _Align::_None; + _Sign _Sgn = _Sign::_None; + bool _Alt = false; + 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}}; }; @@ -663,8 +664,10 @@ public: } constexpr void _On_zero() { - _Specs._Alignment = _Align::_None; - _Specs._Fill[0] = _CharT{'0'}; + _Specs._Leading_zero = true; + if (_Specs._Alignment == _Align::_None) { + _Specs._Fill[0] = _CharT{'0'}; + } } constexpr void _On_width(int _Width) { From 512775f2d2303ddba9c9c8360ed31df616d89b04 Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Mon, 8 Mar 2021 14:26:20 -0800 Subject: [PATCH 18/94] _STD qualify visit_format_arg Co-authored-by: Casey Carter --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 572568968f6..bb7763ff9b5 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1329,7 +1329,7 @@ struct _Format_handler { if (_Begin == _End || *_Begin != '}') { throw format_error("Missing '}' in format string."); } - _Ctx.advance_to(visit_format_arg( + _Ctx.advance_to(_STD visit_format_arg( _Arg_formatter<_OutputIt, _CharT>{ ._Ctx = &_Ctx, ._Specs = &_Specs, From 33d7b1c453dae6b716cfbeae96571a7bbbfdde1f Mon Sep 17 00:00:00 2001 From: Charles Date: Mon, 8 Mar 2021 14:52:30 -0800 Subject: [PATCH 19/94] use addressof instead of the operator --- stl/inc/format | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index bb7763ff9b5..2dd3738dec9 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1331,9 +1331,9 @@ struct _Format_handler { } _Ctx.advance_to(_STD visit_format_arg( _Arg_formatter<_OutputIt, _CharT>{ - ._Ctx = &_Ctx, - ._Specs = &_Specs, - ._Parse_ctx = &_Parse_context, + ._Ctx = _STD addressof(_Ctx), + ._Specs = _STD addressof(_Specs), + ._Parse_ctx = _STD addressof(_Parse_context), }, _Arg)); return _Begin; From 42e81d7ae4ccf1d1411da20c88d501bfec954478 Mon Sep 17 00:00:00 2001 From: Charles Date: Mon, 8 Mar 2021 15:22:29 -0800 Subject: [PATCH 20/94] add a performance note to _On_type check --- stl/inc/format | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stl/inc/format b/stl/inc/format index 2dd3738dec9..04bfcfc6bbb 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -679,6 +679,8 @@ public: } constexpr void _On_type(_CharT _Type) { + // performance note: this could be optimized to one comparison by + // first casting to unsigned int (the negative values will be 128-255) if (_Type < 0 || _Type > (numeric_limits::max)()) { throw format_error("Invalid type specification."); } From 43331bbb875c4507c8b96b37e9c858125b02b913 Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Tue, 9 Mar 2021 15:58:53 -0800 Subject: [PATCH 21/94] : formatting test organization (#1719) * factor out basic format tests for for char type * factor out tests to their own functions * address code review comments. silence warning instead of using _Copy_and_widen --- stl/inc/format | 12 +- .../test.cpp | 357 +++++++++++------- 2 files changed, 242 insertions(+), 127 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 04bfcfc6bbb..f42823ecd6b 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1187,6 +1187,8 @@ _OutputIt _Write(_OutputIt _Out, monostate) { // = 17 + 1 + 1 + 1 + 3 = 24. An example is DBL_MAX which is "-1.7976931348623158e+308". inline constexpr size_t _Format_min_buffer_length = 24; +#pragma warning(push) +#pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch // clang-format off template requires (is_arithmetic_v<_Arithmetic> && !same_as<_Arithmetic, bool> && !same_as<_Arithmetic, _CharT>) @@ -1198,10 +1200,15 @@ _OutputIt _Write(_OutputIt _Out, _Arithmetic _Value) { _STL_ASSERT(_Ec == errc{}, "to_chars failed"); return _STD copy(_Buffer.data(), _End, _Out); } +#pragma warning(pop) template _OutputIt _Write(_OutputIt _Out, bool _Value) { - return _Write(_Out, _Value ? "true" : "false"); + if constexpr (is_same_v<_CharT, wchar_t>) { + return _Write(_Out, _Value ? L"true" : L"false"); + } else { + return _Write(_Out, _Value ? "true" : "false"); + } } template @@ -1210,6 +1217,8 @@ _OutputIt _Write(_OutputIt _Out, _CharT _Value) { return ++_Out; } +#pragma warning(push) +#pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template _OutputIt _Write(_OutputIt _Out, const void* _Value) { // TRANSITION, Reusable buffer @@ -1221,6 +1230,7 @@ _OutputIt _Write(_OutputIt _Out, const void* _Value) { *_Out++ = 'x'; return _STD copy(_Buffer.data(), _End, _Out); } +#pragma warning(pop) template _OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 87bcc9ab9cf..f5e8263b67c 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -7,253 +7,358 @@ #include #include #include +#include +#include using namespace std; -// TODO: fill in tests -template back_insert_iterator std::vformat_to( - back_insert_iterator, const locale&, string_view, format_args_t, char>); +// copied from the string_view tests +template +struct choose_literal; // not defined + +template <> +struct choose_literal { + static constexpr const char* choose(const char* s, const wchar_t*) { + return s; + } +}; + +template <> +struct choose_literal { + static constexpr const wchar_t* choose(const char*, const wchar_t* s) { + return s; + } +}; + +#define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) + +template +auto make_testing_format_args(Args&&... vals) { + if constexpr (is_same_v) { + return make_wformat_args(forward(vals)...); + } else { + return make_format_args(forward(vals)...); + } +} -int main() { - string output_string = ""; - vformat_to(back_insert_iterator(output_string), locale::classic(), "f", make_format_args()); - assert(output_string == "f"); +// tests for format with no format args or replacement fields +template +void test_simple_formatting() { + basic_string output_string; + + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "f"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "f")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "format", make_format_args()); - assert(output_string == "format"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "format"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "format")); +} + +template +void test_escaped_curls() { + basic_string output_string; // test escaped opening curls - output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{{", make_format_args()); - assert(output_string == "{"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{{"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "{")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{{{{", make_format_args()); - assert(output_string == "{{"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{{{{"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "{{")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "x{{", make_format_args()); - assert(output_string == "x{"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "x{{"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "x{")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{{ {{", make_format_args()); - assert(output_string == "{ {"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{{ {{"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "{ {")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "x{{x", make_format_args()); - assert(output_string == "x{x"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "x{{x"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "x{x")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{{x", make_format_args()); - assert(output_string == "{x"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{{x"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "{x")); // tests escaped closing curls output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "}}", make_format_args()); - assert(output_string == "}"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "}}"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "}")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "}}}}", make_format_args()); - assert(output_string == "}}"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "}}}}"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "}}")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "x}}", make_format_args()); - assert(output_string == "x}"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "x}}"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "x}")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "}} }}", make_format_args()); - assert(output_string == "} }"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "}} }}"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "} }")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "x}}x", make_format_args()); - assert(output_string == "x}x"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "x}}x"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "x}x")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "}}x", make_format_args()); - assert(output_string == "}x"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "}}x"), + make_testing_format_args()); + assert(output_string == TYPED_LITERAL(charT, "}x")); +} - output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args("f")); - assert(output_string == "f"); +template +void test_simple_replacement_field() { + basic_string output_string; - output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{0} {0}", make_format_args("f")); - assert(output_string == "f f"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(TYPED_LITERAL(charT, "f"))); + assert(output_string == TYPED_LITERAL(charT, "f")); // Test string_view output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args("f"sv)); - assert(output_string == "f"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(basic_string_view{TYPED_LITERAL(charT, "f")})); + assert(output_string == TYPED_LITERAL(charT, "f")); // Test bool output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(true)); - assert(output_string == "true"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(true)); + assert(output_string == TYPED_LITERAL(charT, "true")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(false)); - assert(output_string == "false"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(false)); + assert(output_string == TYPED_LITERAL(charT, "false")); // Test char output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args('a')); - assert(output_string == "a"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args('a')); + assert(output_string == TYPED_LITERAL(charT, "a")); // Test const void* output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", - make_format_args(static_cast(nullptr))); - assert(output_string == "0x0"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(static_cast(nullptr))); + assert(output_string == TYPED_LITERAL(charT, "0x0")); // Test void* output_string.clear(); - vformat_to( - back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(static_cast(nullptr))); - assert(output_string == "0x0"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(static_cast(nullptr))); + assert(output_string == TYPED_LITERAL(charT, "0x0")); // Test nullptr output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(nullptr)); - assert(output_string == "0x0"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(nullptr)); + assert(output_string == TYPED_LITERAL(charT, "0x0")); // Test signed integers output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(1234)); - assert(output_string == "1234"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(1234)); + assert(output_string == TYPED_LITERAL(charT, "1234")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(1234ll)); - assert(output_string == "1234"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(1234ll)); + assert(output_string == TYPED_LITERAL(charT, "1234")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(INT_MIN)); - assert(output_string == "-2147483648"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(INT_MIN)); + assert(output_string == TYPED_LITERAL(charT, "-2147483648")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(INT_MAX)); - assert(output_string == "2147483647"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(INT_MAX)); + assert(output_string == TYPED_LITERAL(charT, "2147483647")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(LLONG_MAX)); - assert(output_string == "9223372036854775807"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(LLONG_MAX)); + assert(output_string == TYPED_LITERAL(charT, "9223372036854775807")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(LLONG_MIN)); - assert(output_string == "-9223372036854775808"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(LLONG_MIN)); + assert(output_string == TYPED_LITERAL(charT, "-9223372036854775808")); // Test unsigned integers output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(1234u)); - assert(output_string == "1234"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(1234u)); + assert(output_string == TYPED_LITERAL(charT, "1234")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(1234ull)); - assert(output_string == "1234"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(1234ull)); + assert(output_string == TYPED_LITERAL(charT, "1234")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(UINT_MAX)); - assert(output_string == "4294967295"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(UINT_MAX)); + assert(output_string == TYPED_LITERAL(charT, "4294967295")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(ULLONG_MAX)); - assert(output_string == "18446744073709551615"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(ULLONG_MAX)); + assert(output_string == TYPED_LITERAL(charT, "18446744073709551615")); // Test float output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(12.34f)); - assert(output_string == "12.34"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(12.34f)); + assert(output_string == TYPED_LITERAL(charT, "12.34")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(FLT_MAX)); - assert(output_string == "3.4028235e+38"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(FLT_MAX)); + assert(output_string == TYPED_LITERAL(charT, "3.4028235e+38")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(-FLT_MAX)); - assert(output_string == "-3.4028235e+38"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(-FLT_MAX)); + assert(output_string == TYPED_LITERAL(charT, "-3.4028235e+38")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(FLT_MIN)); - assert(output_string == "1.1754944e-38"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(FLT_MIN)); + assert(output_string == TYPED_LITERAL(charT, "1.1754944e-38")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(FLT_EPSILON)); - assert(output_string == "1.1920929e-07"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(FLT_EPSILON)); + assert(output_string == TYPED_LITERAL(charT, "1.1920929e-07")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(FLT_TRUE_MIN)); - assert(output_string == "1e-45"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(FLT_TRUE_MIN)); + assert(output_string == TYPED_LITERAL(charT, "1e-45")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", - make_format_args(numeric_limits::infinity())); - assert(output_string == "inf"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(numeric_limits::infinity())); + assert(output_string == TYPED_LITERAL(charT, "inf")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", - make_format_args(-numeric_limits::infinity())); - assert(output_string == "-inf"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(-numeric_limits::infinity())); + assert(output_string == TYPED_LITERAL(charT, "-inf")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", - make_format_args(numeric_limits::quiet_NaN())); - assert(output_string == "nan"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(numeric_limits::quiet_NaN())); + assert(output_string == TYPED_LITERAL(charT, "nan")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(0.f)); - assert(output_string == "0"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(0.f)); + assert(output_string == TYPED_LITERAL(charT, "0")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(-0.f)); - assert(output_string == "-0"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(-0.f)); + assert(output_string == TYPED_LITERAL(charT, "-0")); // Test double output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(12.34)); - assert(output_string == "12.34"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(12.34)); + assert(output_string == TYPED_LITERAL(charT, "12.34")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(DBL_MAX)); - assert(output_string == "1.7976931348623157e+308"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(DBL_MAX)); + assert(output_string == TYPED_LITERAL(charT, "1.7976931348623157e+308")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(-DBL_MAX)); - assert(output_string == "-1.7976931348623157e+308"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(-DBL_MAX)); + assert(output_string == TYPED_LITERAL(charT, "-1.7976931348623157e+308")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(DBL_MIN)); - assert(output_string == "2.2250738585072014e-308"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(DBL_MIN)); + assert(output_string == TYPED_LITERAL(charT, "2.2250738585072014e-308")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(DBL_EPSILON)); - assert(output_string == "2.220446049250313e-16"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(DBL_EPSILON)); + assert(output_string == TYPED_LITERAL(charT, "2.220446049250313e-16")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(DBL_TRUE_MIN)); - assert(output_string == "5e-324"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(DBL_TRUE_MIN)); + assert(output_string == TYPED_LITERAL(charT, "5e-324")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", - make_format_args(numeric_limits::infinity())); - assert(output_string == "inf"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(numeric_limits::infinity())); + assert(output_string == TYPED_LITERAL(charT, "inf")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", - make_format_args(-numeric_limits::infinity())); - assert(output_string == "-inf"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(-numeric_limits::infinity())); + assert(output_string == TYPED_LITERAL(charT, "-inf")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", - make_format_args(numeric_limits::quiet_NaN())); - assert(output_string == "nan"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(numeric_limits::quiet_NaN())); + assert(output_string == TYPED_LITERAL(charT, "nan")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(0.0)); - assert(output_string == "0"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(0.0)); + assert(output_string == TYPED_LITERAL(charT, "0")); output_string.clear(); - vformat_to(back_insert_iterator(output_string), locale::classic(), "{}", make_format_args(-0.0)); - assert(output_string == "-0"); + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + make_testing_format_args(-0.0)); + assert(output_string == TYPED_LITERAL(charT, "-0")); +} + +template +void test_multiple_replacement_fields() { + basic_string output_string; + + vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{0} {0}"), + make_testing_format_args(TYPED_LITERAL(charT, "f"))); + assert(output_string == TYPED_LITERAL(charT, "f f")); +} + +int main() { + + test_simple_formatting(); + test_simple_formatting(); + + test_escaped_curls(); + test_escaped_curls(); + + test_simple_replacement_field(); + test_simple_replacement_field(); + + test_multiple_replacement_fields(); + test_multiple_replacement_fields(); + return 0; } From 261e10aff9362fe6104da656d7d1acbe92c98c4f Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Wed, 10 Mar 2021 12:51:41 -0800 Subject: [PATCH 22/94] : Add format, vformat, and local-less vformat_to (#1726) * : Add format, vformat, and local-less vformat_to Adds the functions. Only locally tested, as I do not want to mess up the merge of the testing overhaul PR (number). These will be tested through `format`, since it is just a pass-through. * Add inline to functions (d'oh) * Add tests for format, vfgormat, and local-less vformat_to --- stl/inc/format | 64 +++++++++++++++++-- .../test.cpp | 10 +++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index f42823ecd6b..5d3f6728dcc 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1370,6 +1370,22 @@ auto make_wformat_args(const _Args&... _Vals) { template using format_args_t = basic_format_args>; +template +_OutputIt vformat_to(_OutputIt _Out, string_view _Fmt, format_args_t, char> _Args) { + _Format_handler<_OutputIt, char, basic_format_context<_OutputIt, char>> _Handler( + _Out, _Fmt, _Args, locale::classic()); + _Parse_format_string(_Fmt, _Handler); + return _Handler._Ctx.out(); +} + +template +_OutputIt vformat_to(_OutputIt _Out, wstring_view _Fmt, format_args_t, wchar_t> _Args) { + _Format_handler<_OutputIt, wchar_t, basic_format_context<_OutputIt, wchar_t>> _Handler( + _Out, _Fmt, _Args, locale::classic()); + _Parse_format_string(_Fmt, _Handler); + return _Handler._Ctx.out(); +} + template _OutputIt vformat_to( _OutputIt _Out, const locale& _Loc, string_view _Fmt, format_args_t, char> _Args) { @@ -1386,11 +1402,49 @@ _OutputIt vformat_to( return _Handler._Ctx.out(); } -// FUNCTION vformat -string vformat(string_view _Fmt, format_args _Args); -wstring vformat(wstring_view _Fmt, wformat_args _Args); -string vformat(const locale& _Loc, string_view _Fmt, format_args _Args); -wstring vformat(const locale& _Loc, wstring_view _Fmt, wformat_args _Args); +inline string vformat(string_view _Fmt, format_args _Args) { + string _Str; + _STD vformat_to(_STD back_inserter(_Str), _Fmt, _Args); + return _Str; +} + +inline wstring vformat(wstring_view _Fmt, wformat_args _Args) { + wstring _Str; + _STD vformat_to(_STD back_inserter(_Str), _Fmt, _Args); + return _Str; +} + +inline string vformat(const locale& _Loc, string_view _Fmt, format_args _Args) { + string _Str; + _STD vformat_to(_STD back_inserter(_Str), _Loc, _Fmt, _Args); + return _Str; +} + +inline wstring vformat(const locale& _Loc, wstring_view _Fmt, wformat_args _Args) { + wstring _Str; + _STD vformat_to(_STD back_inserter(_Str), _Loc, _Fmt, _Args); + return _Str; +} + +template +string format(string_view _Fmt, const _Types&... _Args) { + return _STD vformat(_Fmt, _STD make_format_args(_Args...)); +} + +template +wstring format(wstring_view _Fmt, const _Types&... _Args) { + return _STD vformat(_Fmt, _STD make_wformat_args(_Args...)); +} + +template +string format(const locale& _Loc, string_view _Fmt, const _Types&... _Args) { + return _STD vformat(_Loc, _Fmt, _STD make_format_args(_Args...)); +} + +template +wstring format(const locale& _Loc, wstring_view _Fmt, const _Types&... _Args) { + return _STD vformat(_Loc, _Fmt, _STD make_wformat_args(_Args...)); +} _STD_END diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index f5e8263b67c..78bbb39fe57 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -31,6 +31,7 @@ struct choose_literal { }; #define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) +#define STR(Str) TYPED_LITERAL(charT, Str) template auto make_testing_format_args(Args&&... vals) { @@ -55,6 +56,11 @@ void test_simple_formatting() { vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "format"), make_testing_format_args()); assert(output_string == TYPED_LITERAL(charT, "format")); + + assert(format(STR("f")) == STR("f")); + assert(format(STR("format")) == STR("format")); + assert(format(locale::classic(), STR("f")) == STR("f")); + assert(format(locale::classic(), STR("format")) == STR("format")); } template @@ -131,6 +137,10 @@ void test_simple_replacement_field() { make_testing_format_args(TYPED_LITERAL(charT, "f"))); assert(output_string == TYPED_LITERAL(charT, "f")); + + assert(format(STR("{}"), STR("f")) == STR("f")); + assert(format(locale::classic(), STR("{}"), STR("f")) == STR("f")); + // Test string_view output_string.clear(); vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), From 3389b78bec4ab5e44ee0b15460e32c4840dd02c9 Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Wed, 10 Mar 2021 17:15:11 -0800 Subject: [PATCH 23/94] : use new STR macro throughout formatting tests (#1729) * use STR instead of TYPED_LITERAL in tests * make specs_setter actually store a reference. --- stl/inc/format | 4 +- .../test.cpp | 290 +++++++++--------- 2 files changed, 139 insertions(+), 155 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 5d3f6728dcc..118e23519ff 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -641,7 +641,7 @@ struct _Basic_format_specs { template class _Specs_setter { public: - explicit constexpr _Specs_setter(_Basic_format_specs<_CharT> _Specs_) : _Specs(_Specs_) {} + explicit constexpr _Specs_setter(_Basic_format_specs<_CharT>& _Specs_) : _Specs(_Specs_) {} constexpr void _On_align(_Align _Aln) { _Specs._Alignment = _Aln; @@ -688,7 +688,7 @@ public: } protected: - _Basic_format_specs<_CharT> _Specs; + _Basic_format_specs<_CharT>& _Specs; }; template diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 78bbb39fe57..331c96b2aaf 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -48,14 +48,13 @@ template void test_simple_formatting() { basic_string output_string; - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "f"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "f")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("f"), make_testing_format_args()); + assert(output_string == STR("f")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "format"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "format")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("format"), make_testing_format_args()); + assert(output_string == STR("format")); assert(format(STR("f")) == STR("f")); assert(format(STR("format")) == STR("format")); @@ -68,74 +67,62 @@ void test_escaped_curls() { basic_string output_string; // test escaped opening curls - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{{"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "{")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{{"), make_testing_format_args()); + assert(output_string == STR("{")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{{{{"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "{{")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{{{{"), make_testing_format_args()); + assert(output_string == STR("{{")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "x{{"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "x{")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("x{{"), make_testing_format_args()); + assert(output_string == STR("x{")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{{ {{"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "{ {")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{{ {{"), make_testing_format_args()); + assert(output_string == STR("{ {")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "x{{x"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "x{x")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("x{{x"), make_testing_format_args()); + assert(output_string == STR("x{x")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{{x"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "{x")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{{x"), make_testing_format_args()); + assert(output_string == STR("{x")); // tests escaped closing curls output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "}}"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "}")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("}}"), make_testing_format_args()); + assert(output_string == STR("}")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "}}}}"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "}}")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("}}}}"), make_testing_format_args()); + assert(output_string == STR("}}")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "x}}"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "x}")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("x}}"), make_testing_format_args()); + assert(output_string == STR("x}")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "}} }}"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "} }")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("}} }}"), make_testing_format_args()); + assert(output_string == STR("} }")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "x}}x"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "x}x")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("x}}x"), make_testing_format_args()); + assert(output_string == STR("x}x")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "}}x"), - make_testing_format_args()); - assert(output_string == TYPED_LITERAL(charT, "}x")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("}}x"), make_testing_format_args()); + assert(output_string == STR("}x")); } template void test_simple_replacement_field() { basic_string output_string; - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(TYPED_LITERAL(charT, "f"))); - assert(output_string == TYPED_LITERAL(charT, "f")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(STR("f"))); + assert(output_string == STR("f")); assert(format(STR("{}"), STR("f")) == STR("f")); @@ -143,217 +130,214 @@ void test_simple_replacement_field() { // Test string_view output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(basic_string_view{TYPED_LITERAL(charT, "f")})); - assert(output_string == TYPED_LITERAL(charT, "f")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), + make_testing_format_args(basic_string_view{STR("f")})); + assert(output_string == STR("f")); // Test bool output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(true)); - assert(output_string == TYPED_LITERAL(charT, "true")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(true)); + assert(output_string == STR("true")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(false)); - assert(output_string == TYPED_LITERAL(charT, "false")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(false)); + assert(output_string == STR("false")); // Test char output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args('a')); - assert(output_string == TYPED_LITERAL(charT, "a")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args('a')); + assert(output_string == STR("a")); // Test const void* output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(static_cast(nullptr))); - assert(output_string == TYPED_LITERAL(charT, "0x0")); + assert(output_string == STR("0x0")); // Test void* output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(static_cast(nullptr))); - assert(output_string == TYPED_LITERAL(charT, "0x0")); + assert(output_string == STR("0x0")); // Test nullptr output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(nullptr)); - assert(output_string == TYPED_LITERAL(charT, "0x0")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(nullptr)); + assert(output_string == STR("0x0")); // Test signed integers output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(1234)); - assert(output_string == TYPED_LITERAL(charT, "1234")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(1234)); + assert(output_string == STR("1234")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(1234ll)); - assert(output_string == TYPED_LITERAL(charT, "1234")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(1234ll)); + assert(output_string == STR("1234")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(INT_MIN)); - assert(output_string == TYPED_LITERAL(charT, "-2147483648")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(INT_MIN)); + assert(output_string == STR("-2147483648")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(INT_MAX)); - assert(output_string == TYPED_LITERAL(charT, "2147483647")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(INT_MAX)); + assert(output_string == STR("2147483647")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(LLONG_MAX)); - assert(output_string == TYPED_LITERAL(charT, "9223372036854775807")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(LLONG_MAX)); + assert(output_string == STR("9223372036854775807")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(LLONG_MIN)); - assert(output_string == TYPED_LITERAL(charT, "-9223372036854775808")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(LLONG_MIN)); + assert(output_string == STR("-9223372036854775808")); // Test unsigned integers output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(1234u)); - assert(output_string == TYPED_LITERAL(charT, "1234")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(1234u)); + assert(output_string == STR("1234")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(1234ull)); - assert(output_string == TYPED_LITERAL(charT, "1234")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(1234ull)); + assert(output_string == STR("1234")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(UINT_MAX)); - assert(output_string == TYPED_LITERAL(charT, "4294967295")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(UINT_MAX)); + assert(output_string == STR("4294967295")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(ULLONG_MAX)); - assert(output_string == TYPED_LITERAL(charT, "18446744073709551615")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(ULLONG_MAX)); + assert(output_string == STR("18446744073709551615")); // Test float output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(12.34f)); - assert(output_string == TYPED_LITERAL(charT, "12.34")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(12.34f)); + assert(output_string == STR("12.34")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(FLT_MAX)); - assert(output_string == TYPED_LITERAL(charT, "3.4028235e+38")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(FLT_MAX)); + assert(output_string == STR("3.4028235e+38")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(-FLT_MAX)); - assert(output_string == TYPED_LITERAL(charT, "-3.4028235e+38")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(-FLT_MAX)); + assert(output_string == STR("-3.4028235e+38")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(FLT_MIN)); - assert(output_string == TYPED_LITERAL(charT, "1.1754944e-38")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(FLT_MIN)); + assert(output_string == STR("1.1754944e-38")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(FLT_EPSILON)); - assert(output_string == TYPED_LITERAL(charT, "1.1920929e-07")); + assert(output_string == STR("1.1920929e-07")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(FLT_TRUE_MIN)); - assert(output_string == TYPED_LITERAL(charT, "1e-45")); + assert(output_string == STR("1e-45")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(numeric_limits::infinity())); - assert(output_string == TYPED_LITERAL(charT, "inf")); + assert(output_string == STR("inf")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(-numeric_limits::infinity())); - assert(output_string == TYPED_LITERAL(charT, "-inf")); + assert(output_string == STR("-inf")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(numeric_limits::quiet_NaN())); - assert(output_string == TYPED_LITERAL(charT, "nan")); + assert(output_string == STR("nan")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(0.f)); - assert(output_string == TYPED_LITERAL(charT, "0")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(0.f)); + assert(output_string == STR("0")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(-0.f)); - assert(output_string == TYPED_LITERAL(charT, "-0")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(-0.f)); + assert(output_string == STR("-0")); // Test double output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(12.34)); - assert(output_string == TYPED_LITERAL(charT, "12.34")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(12.34)); + assert(output_string == STR("12.34")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(DBL_MAX)); - assert(output_string == TYPED_LITERAL(charT, "1.7976931348623157e+308")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(DBL_MAX)); + assert(output_string == STR("1.7976931348623157e+308")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(-DBL_MAX)); - assert(output_string == TYPED_LITERAL(charT, "-1.7976931348623157e+308")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(-DBL_MAX)); + assert(output_string == STR("-1.7976931348623157e+308")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(DBL_MIN)); - assert(output_string == TYPED_LITERAL(charT, "2.2250738585072014e-308")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(DBL_MIN)); + assert(output_string == STR("2.2250738585072014e-308")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(DBL_EPSILON)); - assert(output_string == TYPED_LITERAL(charT, "2.220446049250313e-16")); + assert(output_string == STR("2.220446049250313e-16")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(DBL_TRUE_MIN)); - assert(output_string == TYPED_LITERAL(charT, "5e-324")); + assert(output_string == STR("5e-324")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(numeric_limits::infinity())); - assert(output_string == TYPED_LITERAL(charT, "inf")); + assert(output_string == STR("inf")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(-numeric_limits::infinity())); - assert(output_string == TYPED_LITERAL(charT, "-inf")); + assert(output_string == STR("-inf")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(numeric_limits::quiet_NaN())); - assert(output_string == TYPED_LITERAL(charT, "nan")); + assert(output_string == STR("nan")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(0.0)); - assert(output_string == TYPED_LITERAL(charT, "0")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(0.0)); + assert(output_string == STR("0")); output_string.clear(); - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{}"), - make_testing_format_args(-0.0)); - assert(output_string == TYPED_LITERAL(charT, "-0")); + vformat_to( + back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(-0.0)); + assert(output_string == STR("-0")); } template void test_multiple_replacement_fields() { basic_string output_string; - vformat_to(back_insert_iterator{output_string}, locale::classic(), TYPED_LITERAL(charT, "{0} {0}"), - make_testing_format_args(TYPED_LITERAL(charT, "f"))); - assert(output_string == TYPED_LITERAL(charT, "f f")); + vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("{0} {0}"), + make_testing_format_args(STR("f"))); + assert(output_string == STR("f f")); } int main() { From df935939362c1c0f67e233fc39376e6f39fe44ad Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Tue, 16 Mar 2021 14:21:11 -0700 Subject: [PATCH 24/94] : Add specful writers (#1738) * : Add specful writers Adds specful writers for all types. Supports all specifiers except locale, and does not currently support non-ascii fill characters. I'm sure there's more tests to add, but getting the bulk of it reviewed now seems like a better approach and deficient areas can be identified. * Inline and remove _On_sign that only delegates * Clang-cl doesn't implement P1091R3, so we need to explicitly capture a structured binding. --- stl/inc/format | 463 +++++++++++++++++- .../test.cpp | 408 ++++++++++++++- 2 files changed, 855 insertions(+), 16 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 118e23519ff..cfe245ceabf 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -88,7 +88,7 @@ concept _Parse_arg_id_callbacks = requires(_Ty _At) { { _At._On_manual_id(size_t{}) } -> same_as; }; -template +template concept _Parse_replacement_field_callbacks = requires(_Ty _At, const _CharT* _Begin, const _CharT* _End) { { _At._Parse_context }; { _At._On_text(_Begin, _End) } -> same_as; @@ -96,6 +96,9 @@ concept _Parse_replacement_field_callbacks = requires(_Ty _At, const _CharT* _Be { _At._On_format_specs(size_t{}, _Begin, _End) } -> same_as; }; +template +concept _CharT_or_bool = same_as<_Ty, _CharT> || same_as<_Ty, bool>; + // clang-format on template @@ -665,9 +668,6 @@ public: constexpr void _On_zero() { _Specs._Leading_zero = true; - if (_Specs._Alignment == _Align::_None) { - _Specs._Fill[0] = _CharT{'0'}; - } } constexpr void _On_width(int _Width) { @@ -833,11 +833,6 @@ public: // _On_align has no checking, since we don't implement numeric alignments. - constexpr void _On_sign(_Sign _Sgn) { - _Numeric_checker._Check_sign(); - _Handler::_On_sign(_Sgn); - } - constexpr void _On_hash() { // Note that '#' is not valid for CharT or bool unless you // pass a numeric presentation type, but we encounter '#' before @@ -1191,7 +1186,7 @@ inline constexpr size_t _Format_min_buffer_length = 24; #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch // clang-format off template - requires (is_arithmetic_v<_Arithmetic> && !same_as<_Arithmetic, bool> && !same_as<_Arithmetic, _CharT>) + requires (is_arithmetic_v<_Arithmetic> && !_CharT_or_bool<_Arithmetic, _CharT>) _OutputIt _Write(_OutputIt _Out, _Arithmetic _Value) { // clang-format on // TRANSITION, Reusable buffer @@ -1248,15 +1243,453 @@ _OutputIt _Write(_OutputIt _Out, basic_string_view<_CharT> _Value) { return _STD copy(_Value.begin(), _Value.end(), _Out); } -// TODO: placeholder write -template -_OutputIt _Write(_OutputIt _Out, _Ty _Val, _Basic_format_specs<_CharT>& _Specs) { +template +_OutputIt _Write_aligned( + _OutputIt _Out, int _Width, const _Basic_format_specs<_CharT>& _Specs, _Align _Default_align, _Func&& _Fn) { + int _Fill_left = 0; + int _Fill_right = 0; + auto _Alignment = _Specs._Alignment; + + if (_Alignment == _Align::_None) { + _Alignment = _Default_align; + } + + if (_Width < _Specs._Width) { + switch (_Alignment) { + case _Align::_Left: + _Fill_right = _Specs._Width - _Width; + break; + case _Align::_Right: + _Fill_left = _Specs._Width - _Width; + break; + case _Align::_Center: + _Fill_left = (_Specs._Width - _Width) / 2; + _Fill_right = _Specs._Width - _Width - _Fill_left; + break; + case _Align::_None: + _STL_ASSERT(false, "Invalid alignment"); + break; + } + } + + // TRANSITION, add support for unicode/wide formats + _Out = _STD fill_n(_Out, _Fill_left, _Specs._Fill[0]); + _Out = _Fn(_Out); + return _STD fill_n(_Out, _Fill_right, _Specs._Fill[0]); +} + +template +_NODISCARD constexpr string_view _Get_integral_prefix(char _Type, _Integral _Value) { + switch (_Type) { + case 'b': + return "0b"sv; + case 'B': + return "0B"sv; + case 'x': + return "0x"sv; + case 'X': + return "0X"sv; + case 'o': + if (_Value != _Integral{0}) { + return "0"sv; + } + return {}; + default: + return {}; + } +} + +template +_OutputIt _Write_sign(_OutputIt _Out, _Sign _Sgn, bool _Is_negative) { + if (_Is_negative) { + *_Out = '-'; + } else { + switch (_Sgn) { + case _Sign::_Plus: + *_Out = '+'; + break; + case _Sign::_Space: + *_Out = ' '; + break; + case _Sign::_None: + case _Sign::_Minus: + break; + } + } + return ++_Out; +} + +inline void _Buffer_to_uppercase(char* _Begin, const char* _End) { + for (; _Begin != _End; ++_Begin) { + *_Begin = static_cast(_CSTD toupper(*_Begin)); + } +} + +template +_NODISCARD constexpr bool _In_bounds(_Ty _Value) { + if constexpr (is_unsigned_v<_CharT> && is_unsigned_v<_Ty>) { + return _Value <= (numeric_limits<_CharT>::max)(); + } else if constexpr (is_unsigned_v<_CharT>) { + return _Value >= 0 && static_cast>(_Value) <= (numeric_limits<_CharT>::max)(); + } else if constexpr (is_unsigned_v<_Ty>) { + return _Value <= static_cast>((numeric_limits<_CharT>::max)()); + } else { + return (numeric_limits<_CharT>::min)() <= _Value && _Value <= (numeric_limits<_CharT>::max)(); + } +} + +template +_OutputIt _Write(_OutputIt _Out, monostate, const _Basic_format_specs<_CharT>&) { _STL_INTERNAL_CHECK(false); - (void) _Val; - (void) _Specs; return _Out; } +#pragma warning(push) +#pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch +template +_OutputIt _Write_integral(_OutputIt _Out, _Integral _Value, _Basic_format_specs<_CharT> _Specs) { + if (_Specs._Type == 'c') { + if (!_In_bounds<_CharT>(_Value)) { + throw format_error("integral cannot be stored in charT"); + } + _Specs._Alt = false; + return _Write(_Out, static_cast<_CharT>(_Value), _Specs); + } + + if (_Specs._Precision != -1) { + throw format_error("integral cannot have a precision"); + } + + if (_Specs._Sgn == _Sign::_None) { + _Specs._Sgn = _Sign::_Minus; + } + + int _Base = 10; + bool _To_upper = false; + + switch (_Specs._Type) { + case '\0': + case 'd': + break; + case 'B': + _To_upper = true; + [[fallthrough]]; + case 'b': + _Base = 2; + break; + case 'X': + _To_upper = true; + [[fallthrough]]; + case 'x': + _Base = 16; + break; + case 'o': + _Base = 8; + break; + default: + throw format_error("invalid integral type"); + } + + // long long -1 representation in binary is 64 bits + sign + array _Buffer; + const auto [_End, _Ec] = _STD to_chars(_Buffer.data(), _Buffer.data() + _Buffer.size(), _Value, _Base); + _STL_ASSERT(_Ec == errc{}, "to_chars failed"); + + auto _Buffer_start = _Buffer.data(); + auto _Width = static_cast(_End - _Buffer_start); + + if (_Value >= _Integral{0}) { + if (_Specs._Sgn != _Sign::_Minus) { + _Width += 1; + } + } else { + // Remove the '-', it will be dealt with directly + _Buffer_start += 1; + } + + if (_To_upper) { + _Buffer_to_uppercase(_Buffer_start, _End); + } + + string_view _Prefix; + if (_Specs._Alt) { + _Prefix = _Get_integral_prefix(_Specs._Type, _Value); + _Width += static_cast(_Prefix.size()); + } + + const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Align::_None; + auto _Writer = [&, _End = _End](_OutputIt _Out) { + _Out = _Write_sign(_Out, _Specs._Sgn, _Value < _Integral{0}); + _Out = _STD copy(_Prefix.begin(), _Prefix.end(), _Out); + if (_Write_leading_zeroes && _Width < _Specs._Width) { + _Out = _STD fill_n(_Out, _Specs._Width - _Width, '0'); + } + return _STD copy(_Buffer_start, _End, _Out); + }; + + if (_Write_leading_zeroes) { + return _Writer(_Out); + } + + return _Write_aligned(_Out, _Width, _Specs, _Align::_Right, _Writer); +} +#pragma warning(pop) + +// clang-format off +template + requires (!_CharT_or_bool<_Integral, _CharT>) +_OutputIt _Write(_OutputIt _Out, _Integral _Value, const _Basic_format_specs<_CharT>& _Specs) { + // clang-format on + return _Write_integral(_Out, _Value, _Specs); +} + +template +_OutputIt _Write(_OutputIt _Out, bool _Value, const _Basic_format_specs<_CharT>& _Specs) { + if (_Specs._Type != '\0' && _Specs._Type != 's') { + return _Write_integral(_Out, static_cast(_Value), _Specs); + } + + if (_Specs._Precision != -1) { + throw format_error("bool cannot have a precision"); + } + + if constexpr (is_same_v<_CharT, wchar_t>) { + return _Write(_Out, _Value ? L"true" : L"false", _Specs); + } else { + return _Write(_Out, _Value ? "true" : "false", _Specs); + } +} + +template +_OutputIt _Write(_OutputIt _Out, _CharT _Value, _Basic_format_specs<_CharT> _Specs) { + if (_Specs._Type != '\0' && _Specs._Type != 'c') { + return _Write_integral(_Out, _Value, _Specs); + } + + if (_Specs._Precision != -1) { + throw format_error("charT cannot have a precision"); + } + + // Clear the type so that the string_view writer doesn't fail on 'c'. + _Specs._Type = '\0'; + return _Write(_Out, basic_string_view<_CharT>{&_Value, 1}, _Specs); +} + +#pragma warning(push) +#pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch +template +_OutputIt _Write(_OutputIt _Out, _Float _Value, const _Basic_format_specs<_CharT>& _Specs) { + auto _Sgn = _Specs._Sgn; + if (_Sgn == _Sign::_None) { + _Sgn = _Sign::_Minus; + } + + auto _To_upper = false; + auto _Format = chars_format::general; + auto _Exponent = '\0'; + auto _Precision = _Specs._Precision; + + switch (_Specs._Type) { + case '\0': + break; + case 'A': + _To_upper = true; + [[fallthrough]]; + case 'a': + _Format = chars_format::hex; + _Exponent = 'p'; + break; + case 'E': + _To_upper = true; + [[fallthrough]]; + case 'e': + if (_Precision == -1) { + _Precision = 6; + } + _Format = chars_format::scientific; + _Exponent = 'e'; + break; + case 'F': + case 'f': + if (_Precision == -1) { + _Precision = 6; + } + _Format = chars_format::fixed; + break; + case 'G': + _To_upper = true; + [[fallthrough]]; + case 'g': + if (_Precision == -1) { + _Precision = 6; + } + _Format = chars_format::general; + _Exponent = 'e'; + break; + default: + throw format_error("invalid floating point type"); + } + + array _Buffer; + to_chars_result _Result; + + if (_Precision == -1) { + _Result = _STD to_chars(_Buffer.data(), _Buffer.data() + _Buffer.size(), _Value, _Format); + } else { + _Result = _STD to_chars(_Buffer.data(), _Buffer.data() + _Buffer.size(), _Value, _Format, _Precision); + } + + _STL_ASSERT(_Result.ec == errc{}, "to_chars failed"); + + auto _Buffer_start = _Buffer.data(); + auto _Width = static_cast(_Result.ptr - _Buffer_start); + + const auto _Is_negative = _STD signbit(_Value); + + if (_Is_negative) { + // Remove the '-', it will be dealt with directly + _Buffer_start += 1; + } else { + if (_Sgn != _Sign::_Minus) { + _Width += 1; + } + } + + if (_To_upper) { + _Buffer_to_uppercase(_Buffer_start, _Result.ptr); + _Exponent = static_cast(_CSTD toupper(_Exponent)); + } + + const auto _Is_finite = !_STD isnan(_Value) && !_STD isinf(_Value); + + auto _Append_decimal = false; + auto _Exponent_start = _Result.ptr; + auto _Zeroes_to_append = 0; + + if (_Specs._Alt && _Is_finite) { + auto _Radix_point = _Result.ptr; + for (auto _It = _Buffer_start; _It < _Result.ptr; ++_It) { + if (*_It == '.') { + _Radix_point = _It; + } else if (*_It == _Exponent) { + _Exponent_start = _It; + } + } + if (_Radix_point == _Result.ptr) { + ++_Width; + _Append_decimal = true; + } + + if (_Specs._Type == 'g' || _Specs._Type == 'G') { + _Zeroes_to_append = _Precision - static_cast(_Exponent_start - _Buffer_start); + if (!_Append_decimal) { + _Zeroes_to_append += 1; + } + _Width += _Zeroes_to_append; + } + } + + const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Align::_None; + auto _Writer = [&](_OutputIt _Out) { + _Out = _Write_sign(_Out, _Sgn, _Is_negative); + + if (_Write_leading_zeroes && _Width < _Specs._Width && _Is_finite) { + _Out = _STD fill_n(_Out, _Specs._Width - _Width, '0'); + } + + if (_Specs._Alt) { + _Out = _STD copy(_Buffer_start, _Exponent_start, _Out); + _Buffer_start = _Exponent_start; + + if (_Append_decimal) { + *_Out++ = '.'; + } + for (; _Zeroes_to_append > 0; --_Zeroes_to_append) { + *_Out++ = '0'; + } + } + + return _STD copy(_Buffer_start, _Result.ptr, _Out); + }; + + if (_Write_leading_zeroes && _Is_finite) { + return _Writer(_Out); + } + + return _Write_aligned(_Out, _Width, _Specs, _Align::_Right, _Writer); +} +#pragma warning(pop) + +template +_OutputIt _Write(_OutputIt _Out, const void* _Value, const _Basic_format_specs<_CharT>& _Specs) { + if (_Specs._Type != '\0' && _Specs._Type != 'p') { + throw format_error("invalid const void* type"); + } + + if (_Specs._Sgn != _Sign::_None) { + throw format_error("const void* cannot have a sign"); + } + + if (_Specs._Alt) { + throw format_error("const void* cannot have an alternative representation"); + } + + if (_Specs._Precision != -1) { + throw format_error("const void* cannot have a precision"); + } + + if (_Specs._Leading_zero) { + throw format_error("const void* cannot have a leading zero"); + } + + // 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 hex number represents (log2(16) = log2(2^4) = 4). + // Add 2 for the 0x prefix. + auto _Width = 2 + static_cast(_STD bit_width(reinterpret_cast(_Value)) + 3) / 4; + + // Since the bit width of 0 is 0, special case it instead of complicating the math even more. + if (_Value == nullptr) { + _Width = 3; + } + + return _Write_aligned( + _Out, _Width, _Specs, _Align::_Left, [=](_OutputIt _Out) { return _Write<_CharT>(_Out, _Value); }); +} + +template +_OutputIt _Write(_OutputIt _Out, const _CharT* _Value, const _Basic_format_specs<_CharT>& _Specs) { + return _Write(_Out, basic_string_view<_CharT>{_Value}, _Specs); +} + +template +_OutputIt _Write(_OutputIt _Out, basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs) { + if (_Specs._Type != '\0' && _Specs._Type != 's') { + throw format_error("invalid string type"); + } + + if (_Specs._Sgn != _Sign::_None) { + throw format_error("string cannot have a sign"); + } + + if (_Specs._Alt) { + throw format_error("string cannot have an alternative representation"); + } + + if (_Specs._Leading_zero) { + throw format_error("string cannot have a leading zero"); + } + + auto _Printed_size = static_cast(_Value.size()); + + if (_Specs._Precision != -1 && _Printed_size > _Specs._Precision) { + _Printed_size = _Specs._Precision; + } + + return _Write_aligned(_Out, _Printed_size, _Specs, _Align::_Left, + [=](_OutputIt _Out) { return _Write(_Out, _Value.substr(size_t{0}, static_cast(_Printed_size))); }); +} + // This is the visitor that's used for "simple" replacement fields, // it could be a generic lambda (with overloaded), but that's // bad for throughput. A simple replacement field is a replacement field diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 331c96b2aaf..a380c2f8cda 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,15 @@ auto make_testing_format_args(Args&&... vals) { } } +template +void throw_helper(const charT* fmt, const Args&... vals) { + try { + format(fmt, vals...); + assert(false); + } catch (const format_error&) { + } +} + // tests for format with no format args or replacement fields template @@ -340,8 +350,398 @@ void test_multiple_replacement_fields() { assert(output_string == STR("f f")); } -int main() { +template +void test_fill_and_align() { + auto writer = [](auto out) { + out++ = 'A'; + out++ = 'B'; + return out; + }; + + _Basic_format_specs specs; + + auto tester = [&] { + basic_string output_string; + _Write_aligned(back_inserter(output_string), 2, specs, _Align::_Left, writer); + return output_string; + }; + + assert(tester() == STR("AB")); + + specs._Width = 1; + assert(tester() == STR("AB")); + + + specs._Width = 5; + specs._Alignment = _Align::_Left; + assert(tester() == STR("AB ")); + + specs._Alignment = _Align::_Right; + assert(tester() == STR(" AB")); + + specs._Alignment = _Align::_Center; + assert(tester() == STR(" AB ")); + + + specs._Alignment = _Align::_Left; + specs._Fill[0] = {'*'}; + assert(tester() == STR("AB***")); + + specs._Alignment = _Align::_Right; + assert(tester() == STR("***AB")); + + specs._Alignment = _Align::_Center; + assert(tester() == STR("*AB**")); +} +template +void test_intergal_specs() { + assert(format(STR("{:}"), integral{0}) == STR("0")); + + // Sign + assert(format(STR("{: }"), integral{0}) == STR(" 0")); + assert(format(STR("{:+}"), integral{0}) == STR("+0")); + assert(format(STR("{:-}"), integral{0}) == STR("0")); + + if constexpr (is_signed_v) { + assert(format(STR("{: }"), integral{-1}) == STR("-1")); + assert(format(STR("{:+}"), integral{-1}) == STR("-1")); + assert(format(STR("{:-}"), integral{-1}) == STR("-1")); + } + + assert(format(STR("{: 3}"), integral{1}) == STR(" 1")); + assert(format(STR("{:+3}"), integral{1}) == STR(" +1")); + assert(format(STR("{:-3}"), integral{1}) == STR(" 1")); + + // Alternate form + assert(format(STR("{:#}"), integral{0}) == STR("0")); + assert(format(STR("{:#d}"), integral{0}) == STR("0")); + assert(format(STR("{:#c}"), integral{'a'}) == STR("a")); + + assert(format(STR("{:#b}"), integral{0}) == STR("0b0")); + assert(format(STR("{:#B}"), integral{0}) == STR("0B0")); + + assert(format(STR("{:#o}"), integral{0}) == STR("0")); + assert(format(STR("{:#o}"), integral{1}) == STR("01")); + + assert(format(STR("{:#x}"), integral{0}) == STR("0x0")); + assert(format(STR("{:#X}"), integral{0}) == STR("0X0")); + assert(format(STR("{:#x}"), integral{255}) == STR("0xff")); + assert(format(STR("{:#X}"), integral{255}) == STR("0XFF")); + + assert(format(STR("{:+#6x}"), integral{255}) == STR(" +0xff")); + + if constexpr (is_signed_v) { + assert(format(STR("{:#o}"), integral{-1}) == STR("-01")); + assert(format(STR("{:#x}"), integral{-255}) == STR("-0xff")); + assert(format(STR("{:#X}"), integral{-255}) == STR("-0XFF")); + } + + // Leading zero + assert(format(STR("{:0}"), integral{0}) == STR("0")); + assert(format(STR("{:03}"), integral{0}) == STR("000")); + assert(format(STR("{:+03}"), integral{0}) == STR("+00")); + assert(format(STR("{:<03}"), integral{0}) == STR("0 ")); + assert(format(STR("{:>03}"), integral{0}) == STR(" 0")); + assert(format(STR("{:+#06X}"), integral{5}) == STR("+0X005")); + + // Width + assert(format(STR("{:3}"), integral{0}) == STR(" 0")); + + // Precision + throw_helper(STR("{:.1}"), integral{0}); + + // Type + assert(format(STR("{:b}"), integral{0}) == STR("0")); + assert(format(STR("{:b}"), integral{100}) == STR("1100100")); + + assert(format(STR("{:d}"), integral{100}) == STR("100")); + + throw_helper(STR("{:c}"), integral{numeric_limits::max()} + 1); + if constexpr (is_signed_v) { + throw_helper(STR("{:c}"), integral{numeric_limits::min()} - 1); + } +} + +template +void test_type(const charT* fmt, T val) { + assert(format(fmt, val) == format(fmt, static_cast(val))); +} + +template +void test_bool_specs() { + assert(format(STR("{:}"), true) == STR("true")); + assert(format(STR("{:}"), false) == STR("false")); + + // Sign + throw_helper(STR("{: }"), true); + throw_helper(STR("{:+}"), true); + throw_helper(STR("{:-}"), true); + + // Alternate form + throw_helper(STR("{:#}"), true); + + // Leading zero + throw_helper(STR("{:0}"), true); + + // Width + assert(format(STR("{:6}"), true) == STR("true ")); + assert(format(STR("{:6}"), false) == STR("false ")); + + // Precision + throw_helper(STR("{:.5}"), true); + + // Type + assert(format(STR("{:s}"), true) == STR("true")); + throw_helper(STR("{:a}"), true); + + test_type(STR("{:b}"), true); + test_type(STR("{:B}"), true); + test_type(STR("{:c}"), true); + test_type(STR("{:d}"), true); + test_type(STR("{:o}"), true); + test_type(STR("{:x}"), true); + test_type(STR("{:X}"), true); + + test_type(STR("{:b}"), false); + test_type(STR("{:B}"), false); + test_type(STR("{:c}"), false); + test_type(STR("{:d}"), false); + test_type(STR("{:o}"), false); + test_type(STR("{:x}"), false); + test_type(STR("{:X}"), false); +} + +template +void test_char_specs() { + assert(format(STR("{:}"), charT{'X'}) == STR("X")); + + // Sign + throw_helper(STR("{: }"), charT{'X'}); + throw_helper(STR("{:+}"), charT{'X'}); + throw_helper(STR("{:-}"), charT{'X'}); + + // Alternate form + throw_helper(STR("{:#}"), charT{'X'}); + + // Leading zero + throw_helper(STR("{:0}"), charT{'X'}); + + // Width + assert(format(STR("{:3}"), charT{'X'}) == STR("X ")); + + // Precision + throw_helper(STR("{:.5}"), charT{'X'}); + + // Types + assert(format(STR("{:c}"), charT{'X'}) == STR("X")); + throw_helper(STR("{:a}"), charT{'X'}); + + test_type(STR("{:b}"), charT{'X'}); + test_type(STR("{:B}"), charT{'X'}); + test_type(STR("{:c}"), charT{'X'}); + test_type(STR("{:d}"), charT{'X'}); + test_type(STR("{:o}"), charT{'X'}); + test_type(STR("{:x}"), charT{'X'}); + test_type(STR("{:X}"), charT{'X'}); + + test_type(STR("{:+d}"), charT{'X'}); +} + +template +void test_float_specs() { + const Float inf = numeric_limits::infinity(); + const Float nan = numeric_limits::quiet_NaN(); + + assert(format(STR("{:}"), Float{0}) == STR("0")); + assert(format(STR("{:}"), inf) == STR("inf")); + assert(format(STR("{:}"), nan) == STR("nan")); + + // Sign + assert(format(STR("{: }"), Float{0}) == STR(" 0")); + assert(format(STR("{:+}"), Float{0}) == STR("+0")); + assert(format(STR("{:-}"), Float{0}) == STR("0")); + + assert(format(STR("{: }"), Float{-1}) == STR("-1")); + assert(format(STR("{:+}"), Float{-1}) == STR("-1")); + assert(format(STR("{:-}"), Float{-1}) == STR("-1")); + + assert(format(STR("{: 3}"), Float{1}) == STR(" 1")); + assert(format(STR("{:+3}"), Float{1}) == STR(" +1")); + assert(format(STR("{:-3}"), Float{1}) == STR(" 1")); + + assert(format(STR("{: }"), inf) == STR(" inf")); + assert(format(STR("{:+}"), inf) == STR("+inf")); + assert(format(STR("{:-}"), inf) == STR("inf")); + + assert(format(STR("{: }"), -inf) == STR("-inf")); + assert(format(STR("{:+}"), -inf) == STR("-inf")); + assert(format(STR("{:-}"), -inf) == STR("-inf")); + + assert(format(STR("{: }"), nan) == STR(" nan")); + assert(format(STR("{:+}"), nan) == STR("+nan")); + assert(format(STR("{:-}"), nan) == STR("nan")); + + // Alternate form + assert(format(STR("{:#}"), Float{0}) == STR("0.")); + assert(format(STR("{:#a}"), Float{0}) == STR("0.p+0")); + assert(format(STR("{:#A}"), Float{0}) == STR("0.P+0")); + assert(format(STR("{:#.0e}"), Float{0}) == STR("0.e+00")); + assert(format(STR("{:#.0E}"), Float{0}) == STR("0.E+00")); + assert(format(STR("{:#.0f}"), Float{0}) == STR("0.")); + assert(format(STR("{:#.0F}"), Float{0}) == STR("0.")); + assert(format(STR("{:#g}"), Float{0}) == STR("0.00000")); + assert(format(STR("{:#G}"), Float{0}) == STR("0.00000")); + assert(format(STR("{:#g}"), Float{1.2}) == STR("1.20000")); + assert(format(STR("{:#G}"), Float{1.2}) == STR("1.20000")); + assert(format(STR("{:#g}"), Float{1'000'000}) == STR("1.00000e+06")); + assert(format(STR("{:#g}"), Float{12.2}) == STR("12.2000")); + assert(format(STR("{:#.0g}"), Float{0}) == STR("0.")); + assert(format(STR("{:#.0G}"), Float{0}) == STR("0.")); + + assert(format(STR("{:#} {:#}"), inf, nan) == STR("inf nan")); + assert(format(STR("{:#a} {:#a}"), inf, nan) == STR("inf nan")); + assert(format(STR("{:#A} {:#A}"), inf, nan) == STR("INF NAN")); + assert(format(STR("{:#e} {:#e}"), inf, nan) == STR("inf nan")); + assert(format(STR("{:#E} {:#E}"), inf, nan) == STR("INF NAN")); + assert(format(STR("{:#f} {:#f}"), inf, nan) == STR("inf nan")); + assert(format(STR("{:#F} {:#F}"), inf, nan) == STR("inf nan")); + assert(format(STR("{:#g} {:#g}"), inf, nan) == STR("inf nan")); + assert(format(STR("{:#G} {:#G}"), inf, nan) == STR("INF NAN")); + + // Width + assert(format(STR("{:3}"), Float{0}) == STR(" 0")); + assert(format(STR("{:#9G}"), Float{12.2}) == STR(" 12.2000")); + assert(format(STR("{:#12g}"), Float{1'000'000}) == STR(" 1.00000e+06")); + + // Precision + Float value = 1234.52734375; + assert(format(STR("{:.4}"), value) == STR("1235")); + assert(format(STR("{:.1}"), inf) == STR("inf")); + assert(format(STR("{:.1}"), nan) == STR("nan")); + + assert(format(STR("{:.4a}"), value) == STR("1.34a2p+10")); + assert(format(STR("{:.4A}"), value) == STR("1.34A2P+10")); + + assert(format(STR("{:.4e}"), value) == STR("1.2345e+03")); + assert(format(STR("{:.4E}"), value) == STR("1.2345E+03")); + + assert(format(STR("{:.4f}"), value) == STR("1234.5273")); + assert(format(STR("{:.4F}"), value) == STR("1234.5273")); + + assert(format(STR("{:.4g}"), value) == STR("1235")); + assert(format(STR("{:.4G}"), value) == STR("1235")); + + // Leading zero + assert(format(STR("{:06}"), Float{0}) == STR("000000")); + assert(format(STR("{:06}"), Float{1.2}) == STR("0001.2")); + assert(format(STR("{:06}"), nan) == STR(" nan")); + assert(format(STR("{:06}"), inf) == STR(" inf")); + + // Type + assert(format(STR("{:a}"), value) == STR("1.34a1cp+10")); + assert(format(STR("{:A}"), value) == STR("1.34A1CP+10")); + + assert(format(STR("{:e}"), value) == STR("1.234527e+03")); + assert(format(STR("{:E}"), value) == STR("1.234527E+03")); + + assert(format(STR("{:f}"), value) == STR("1234.527344")); + assert(format(STR("{:F}"), value) == STR("1234.527344")); + + assert(format(STR("{:g}"), value) == STR("1234.53")); + assert(format(STR("{:G}"), value) == STR("1234.53")); +} + +template +void test_pointer_specs() { + assert(format(STR("{:}"), nullptr) == STR("0x0")); + + // Sign + throw_helper(STR("{: }"), nullptr); + throw_helper(STR("{:+}"), nullptr); + throw_helper(STR("{:-}"), nullptr); + + // Alternate form + throw_helper(STR("{:#}"), nullptr); + + // Leading zero + throw_helper(STR("{:0}"), nullptr); + + // Width + assert(format(STR("{:5}"), nullptr) == STR("0x0 ")); + + // Precision + throw_helper(STR("{:.5}"), nullptr); + + // Types + assert(format(STR("{:p}"), nullptr) == STR("0x0")); + throw_helper(STR("{:a}"), nullptr); +} + +template +void test_string_specs() { + auto cstr = STR("scully"); + auto view = basic_string_view{cstr}; + + assert(format(STR("{:}"), cstr) == cstr); + assert(format(STR("{:}"), view) == cstr); + + // Sign + throw_helper(STR("{: }"), cstr); + throw_helper(STR("{:+}"), cstr); + throw_helper(STR("{:-}"), cstr); + + throw_helper(STR("{: }"), view); + throw_helper(STR("{:+}"), view); + throw_helper(STR("{:-}"), view); + + // Alternate form + throw_helper(STR("{:#}"), cstr); + throw_helper(STR("{:#}"), view); + + // Leading zero + throw_helper(STR("{:0}"), cstr); + throw_helper(STR("{:0}"), view); + + // Width + assert(format(STR("{:8}"), cstr) == STR("scully ")); + assert(format(STR("{:8}"), view) == STR("scully ")); + + // Precision + assert(format(STR("{:.2}"), cstr) == STR("sc")); + assert(format(STR("{:5.2}"), cstr) == STR("sc ")); + assert(format(STR("{:5.2}"), cstr) == STR("sc ")); + assert(format(STR("{:>5.2}"), cstr) == STR(" sc")); + + assert(format(STR("{:.2}"), view) == STR("sc")); + assert(format(STR("{:5.2}"), view) == STR("sc ")); + assert(format(STR("{:5.2}"), view) == STR("sc ")); + assert(format(STR("{:>5.2}"), view) == STR(" sc")); + + // Types + assert(format(STR("{:s}"), cstr) == cstr); + throw_helper(STR("{:a}"), cstr); + + assert(format(STR("{:s}"), view) == cstr); + throw_helper(STR("{:a}"), view); +} + +template +void test_spec_replacement_field() { + test_intergal_specs(); + test_intergal_specs(); + test_intergal_specs(); + test_intergal_specs(); + test_bool_specs(); + test_char_specs(); + test_float_specs(); + test_float_specs(); + test_float_specs(); + test_pointer_specs(); + test_string_specs(); +} + +int main() { test_simple_formatting(); test_simple_formatting(); @@ -354,5 +754,11 @@ int main() { test_multiple_replacement_fields(); test_multiple_replacement_fields(); + test_fill_and_align(); + test_fill_and_align(); + + test_spec_replacement_field(); + test_spec_replacement_field(); + return 0; } From 927a3b5e5fcc332fa2c17acfd666f36c90517f32 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Thu, 18 Mar 2021 14:09:42 -0700 Subject: [PATCH 25/94] : Add const to some parameters (#1745) I added `const` to the some parameters where it made logical sense. Sometimes the output iterator could be made constant, but that doesn't seem correct to me. Also, I avoided making pointers to cstrings const, because logically they need to be iterated over (as opposed to void*, which doesn't). It feels a little verbose. --- stl/inc/format | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index cfe245ceabf..b40943dce7c 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1187,7 +1187,7 @@ inline constexpr size_t _Format_min_buffer_length = 24; // clang-format off template requires (is_arithmetic_v<_Arithmetic> && !_CharT_or_bool<_Arithmetic, _CharT>) -_OutputIt _Write(_OutputIt _Out, _Arithmetic _Value) { +_OutputIt _Write(_OutputIt _Out, const _Arithmetic _Value) { // clang-format on // TRANSITION, Reusable buffer array _Buffer; @@ -1198,7 +1198,7 @@ _OutputIt _Write(_OutputIt _Out, _Arithmetic _Value) { #pragma warning(pop) template -_OutputIt _Write(_OutputIt _Out, bool _Value) { +_OutputIt _Write(_OutputIt _Out, const bool _Value) { if constexpr (is_same_v<_CharT, wchar_t>) { return _Write(_Out, _Value ? L"true" : L"false"); } else { @@ -1207,7 +1207,7 @@ _OutputIt _Write(_OutputIt _Out, bool _Value) { } template -_OutputIt _Write(_OutputIt _Out, _CharT _Value) { +_OutputIt _Write(_OutputIt _Out, const _CharT _Value) { *_Out = _Value; return ++_Out; } @@ -1215,7 +1215,7 @@ _OutputIt _Write(_OutputIt _Out, _CharT _Value) { #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template -_OutputIt _Write(_OutputIt _Out, const void* _Value) { +_OutputIt _Write(_OutputIt _Out, const void* const _Value) { // TRANSITION, Reusable buffer array _Buffer; const auto [_End, _Ec] = @@ -1239,13 +1239,13 @@ _OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { } template -_OutputIt _Write(_OutputIt _Out, basic_string_view<_CharT> _Value) { +_OutputIt _Write(_OutputIt _Out, const basic_string_view<_CharT> _Value) { return _STD copy(_Value.begin(), _Value.end(), _Out); } template -_OutputIt _Write_aligned( - _OutputIt _Out, int _Width, const _Basic_format_specs<_CharT>& _Specs, _Align _Default_align, _Func&& _Fn) { +_OutputIt _Write_aligned(_OutputIt _Out, const int _Width, const _Basic_format_specs<_CharT>& _Specs, + const _Align _Default_align, _Func&& _Fn) { int _Fill_left = 0; int _Fill_right = 0; auto _Alignment = _Specs._Alignment; @@ -1279,7 +1279,7 @@ _OutputIt _Write_aligned( } template -_NODISCARD constexpr string_view _Get_integral_prefix(char _Type, _Integral _Value) { +_NODISCARD constexpr string_view _Get_integral_prefix(const char _Type, const _Integral _Value) { switch (_Type) { case 'b': return "0b"sv; @@ -1300,7 +1300,7 @@ _NODISCARD constexpr string_view _Get_integral_prefix(char _Type, _Integral _Val } template -_OutputIt _Write_sign(_OutputIt _Out, _Sign _Sgn, bool _Is_negative) { +_OutputIt _Write_sign(_OutputIt _Out, const _Sign _Sgn, const bool _Is_negative) { if (_Is_negative) { *_Out = '-'; } else { @@ -1326,7 +1326,7 @@ inline void _Buffer_to_uppercase(char* _Begin, const char* _End) { } template -_NODISCARD constexpr bool _In_bounds(_Ty _Value) { +_NODISCARD constexpr bool _In_bounds(const _Ty _Value) { if constexpr (is_unsigned_v<_CharT> && is_unsigned_v<_Ty>) { return _Value <= (numeric_limits<_CharT>::max)(); } else if constexpr (is_unsigned_v<_CharT>) { @@ -1347,7 +1347,7 @@ _OutputIt _Write(_OutputIt _Out, monostate, const _Basic_format_specs<_CharT>&) #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template -_OutputIt _Write_integral(_OutputIt _Out, _Integral _Value, _Basic_format_specs<_CharT> _Specs) { +_OutputIt _Write_integral(_OutputIt _Out, const _Integral _Value, _Basic_format_specs<_CharT> _Specs) { if (_Specs._Type == 'c') { if (!_In_bounds<_CharT>(_Value)) { throw format_error("integral cannot be stored in charT"); @@ -1438,13 +1438,13 @@ _OutputIt _Write_integral(_OutputIt _Out, _Integral _Value, _Basic_format_specs< // clang-format off template requires (!_CharT_or_bool<_Integral, _CharT>) -_OutputIt _Write(_OutputIt _Out, _Integral _Value, const _Basic_format_specs<_CharT>& _Specs) { +_OutputIt _Write(_OutputIt _Out, const _Integral _Value, const _Basic_format_specs<_CharT>& _Specs) { // clang-format on return _Write_integral(_Out, _Value, _Specs); } template -_OutputIt _Write(_OutputIt _Out, bool _Value, const _Basic_format_specs<_CharT>& _Specs) { +_OutputIt _Write(_OutputIt _Out, const bool _Value, const _Basic_format_specs<_CharT>& _Specs) { if (_Specs._Type != '\0' && _Specs._Type != 's') { return _Write_integral(_Out, static_cast(_Value), _Specs); } @@ -1461,7 +1461,7 @@ _OutputIt _Write(_OutputIt _Out, bool _Value, const _Basic_format_specs<_CharT>& } template -_OutputIt _Write(_OutputIt _Out, _CharT _Value, _Basic_format_specs<_CharT> _Specs) { +_OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs) { if (_Specs._Type != '\0' && _Specs._Type != 'c') { return _Write_integral(_Out, _Value, _Specs); } @@ -1478,7 +1478,7 @@ _OutputIt _Write(_OutputIt _Out, _CharT _Value, _Basic_format_specs<_CharT> _Spe #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template -_OutputIt _Write(_OutputIt _Out, _Float _Value, const _Basic_format_specs<_CharT>& _Specs) { +_OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs<_CharT>& _Specs) { auto _Sgn = _Specs._Sgn; if (_Sgn == _Sign::_None) { _Sgn = _Sign::_Minus; @@ -1621,7 +1621,7 @@ _OutputIt _Write(_OutputIt _Out, _Float _Value, const _Basic_format_specs<_CharT #pragma warning(pop) template -_OutputIt _Write(_OutputIt _Out, const void* _Value, const _Basic_format_specs<_CharT>& _Specs) { +_OutputIt _Write(_OutputIt _Out, const void* const _Value, const _Basic_format_specs<_CharT>& _Specs) { if (_Specs._Type != '\0' && _Specs._Type != 'p') { throw format_error("invalid const void* type"); } @@ -1663,7 +1663,7 @@ _OutputIt _Write(_OutputIt _Out, const _CharT* _Value, const _Basic_format_specs } template -_OutputIt _Write(_OutputIt _Out, basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs) { +_OutputIt _Write(_OutputIt _Out, const basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs) { if (_Specs._Type != '\0' && _Specs._Type != 's') { throw format_error("invalid string type"); } From 63f46aded496d384992cdbd809bbfa0c11940548 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Thu, 18 Mar 2021 14:16:48 -0700 Subject: [PATCH 26/94] : Add localized arg parsing support (#1746) * : Add localized arg parsing support Just teaches the arg parser to set the localized spec. * Fix extra increment --- stl/inc/format | 17 ++++++++++++++++- .../P0645R10_text_formatting_parsing/test.cpp | 8 +++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index b40943dce7c..d4d2ca94364 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -80,6 +80,7 @@ concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, { _At._On_sign(_Sgn) } -> same_as; { _At._On_hash() } -> same_as; { _At._On_zero() } -> same_as; + { _At._On_localized() } -> same_as; { _At._On_type(_CharT{}) } -> same_as; }; template @@ -541,10 +542,20 @@ constexpr const _CharT* _Parse_format_specs(const _CharT* _Begin, const _CharT* if (*_Begin == '.') { _Begin = _Parse_precision(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } + } + + if (*_Begin == 'L') { + _Callbacks._On_localized(); + if (++_Begin == _End) { + return _Begin; + } } // If there's anything remaining we assume it's a type. - if (_Begin != _End && *_Begin != '}') { + if (*_Begin != '}') { _Callbacks._On_type(*_Begin++); } return _Begin; @@ -678,6 +689,10 @@ public: _Specs._Precision = _Precision; } + constexpr void _On_localized() { + _Specs._Localized = true; + } + constexpr void _On_type(_CharT _Type) { // performance note: this could be optimized to one comparison by // first casting to unsigned int (the negative values will be 128-255) diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index 831ff50fab4..d513ba4d914 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -43,6 +43,7 @@ struct noop_testing_callbacks { constexpr void _On_sign(_Sign) {} constexpr void _On_hash() {} constexpr void _On_zero() {} + constexpr void _On_localized() {} constexpr void _On_type(CharT) {} }; @@ -59,6 +60,7 @@ struct testing_callbacks { bool expected_auto_dynamic_precision = false; bool expected_hash = false; bool expected_zero = false; + bool expected_localized = false; CharT expected_type = '\0'; constexpr void _On_align(_Align aln) { assert(aln == expected_alignment); @@ -93,6 +95,9 @@ struct testing_callbacks { constexpr void _On_zero() { assert(expected_zero); } + constexpr void _On_localized() { + assert(expected_localized); + } constexpr void _On_type(CharT type) { assert(type == expected_type); } @@ -244,7 +249,7 @@ constexpr bool test_parse_format_specs() { view_typ s3(TYPED_LITERAL(CharT, "*^6}")); view_typ s4(TYPED_LITERAL(CharT, "6d}")); view_typ s5(TYPED_LITERAL(CharT, "*^+4.4a}")); - view_typ s6(TYPED_LITERAL(CharT, "*^+#04.4a}")); + view_typ s6(TYPED_LITERAL(CharT, "*^+#04.4La}")); test_parse_helper(parse_format_specs_fn, s0, false, s0.size() - 1, {.expected_width = 6}); test_parse_helper(parse_format_specs_fn, s1, false, s1.size(), {.expected_alignment = _Align::_Left, @@ -274,6 +279,7 @@ constexpr bool test_parse_format_specs() { .expected_precision = 4, .expected_hash = true, .expected_zero = true, + .expected_localized = true, .expected_type = 'a'}); return true; } From 94116a6e75a4aef8e5962297a0c704a896d6370b Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Thu, 18 Mar 2021 17:03:24 -0700 Subject: [PATCH 27/94] : basic custom formatter test (#1739) * add tests for basic custom formatter. --- stl/inc/format | 56 +++++++++++++++++-- tests/std/test.lst | 1 + .../env.lst | 4 ++ .../test.cpp | 42 ++++++++++++++ 4 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 tests/std/tests/P0645R10_text_formatting_custom_formatting/env.lst create mode 100644 tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp diff --git a/stl/inc/format b/stl/inc/format index d4d2ca94364..5be09ff73a4 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -175,7 +175,7 @@ public: class handle { private: const void* _Ptr; - void (*_Format)(basic_format_parse_context<_CharType>& _Parse_ctx, _Context _Format_ctx, const void*); + void (*_Format)(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx, const void*); friend basic_format_arg; public: @@ -876,6 +876,9 @@ concept _Has_formatter = requires(const _Ty& _Val, _Context& _Ctx) { }; // clang-format on +template +constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type); + // See N4878 [format.arg]/5 // clang-format off template @@ -985,7 +988,7 @@ private: void _Store_impl(const size_t _Arg_index, const _Basic_format_arg_type _Arg_type, _Ty _Val) noexcept { const auto _Index_array = reinterpret_cast<_Index_type*>(_Storage); const auto _Store_index = _Index_array[_Arg_index]._Index; - const auto _Length = sizeof(_Get_format_arg_storage_type<_Context>(_Val)); + const auto _Length = _Get_format_arg_type_storage_size<_CharType>(_Arg_type); _CSTD memcpy(_Storage + _Index_length + _Store_index, _STD addressof(_Val), _Length); _Index_array[_Arg_index]._Type = _Arg_type; @@ -1001,8 +1004,8 @@ private: requires _Has_formatter<_Context, _Ty> void _Store(const size_t _Arg_index, const _Ty& _Val) noexcept { // clang-format on - _Store_impl::handle>( - _Arg_index, _Basic_format_arg_type::_Custom_type, basic_format_arg<_Context>::handle(_Val)); + using _Handle_type = typename basic_format_arg<_Context>::handle; + _Store_impl<_Handle_type>(_Arg_index, _Basic_format_arg_type::_Custom_type, _Handle_type{_Val}); } // clang-format off @@ -1185,6 +1188,42 @@ public: } }; +template +constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type) { + switch (_Type) { + case _Basic_format_arg_type::_Int_type: + return sizeof(int); + case _Basic_format_arg_type::_UInt_type: + return sizeof(unsigned int); + case _Basic_format_arg_type::_Long_long_type: + return sizeof(long long); + case _Basic_format_arg_type::_ULong_long_type: + return sizeof(unsigned long long); + case _Basic_format_arg_type::_Bool_type: + return sizeof(bool); + case _Basic_format_arg_type::_Char_type: + return sizeof(_CharT); + case _Basic_format_arg_type::_Float_type: + return sizeof(float); + case _Basic_format_arg_type::_Double_type: + return sizeof(double); + case _Basic_format_arg_type::_Long_double_type: + return sizeof(long double); + case _Basic_format_arg_type::_Pointer_type: + return sizeof(void*); + case _Basic_format_arg_type::_CString_type: + return sizeof(const _CharT*); + case _Basic_format_arg_type::_String_type: + return sizeof(basic_string_view<_CharT>); + case _Basic_format_arg_type::_Custom_type: + return sizeof(void*) + sizeof(void (*)()); + case _Basic_format_arg_type::_None: + default: + _STL_INTERNAL_CHECK(false); + return 0; + } +} + template _OutputIt _Write(_OutputIt _Out, monostate) { _STL_INTERNAL_CHECK(false); @@ -1800,6 +1839,15 @@ struct _Format_handler { } }; +// Generic formatter definition, the deleted default constructor +// makes it "disabled" as per N4868 [formatter.formatter.spec]/5 +template +struct formatter { + formatter() = delete; + formatter(const formatter&) = delete; + formatter operator=(const formatter&) = delete; +}; + using format_context = basic_format_context, string::value_type>; using wformat_context = basic_format_context, wstring::value_type>; using format_args = basic_format_args; diff --git a/tests/std/test.lst b/tests/std/test.lst index 7fad59a43b6..1697cba92da 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -257,6 +257,7 @@ tests\P0608R3_improved_variant_converting_constructor tests\P0616R0_using_move_in_numeric tests\P0631R8_numbers_math_constants tests\P0645R10_text_formatting_args +tests\P0645R10_text_formatting_custom_formatting tests\P0645R10_text_formatting_death tests\P0645R10_text_formatting_formatting tests\P0645R10_text_formatting_parse_contexts diff --git a/tests/std/tests/P0645R10_text_formatting_custom_formatting/env.lst b/tests/std/tests/P0645R10_text_formatting_custom_formatting/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_custom_formatting/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp new file mode 100644 index 00000000000..8fa5a1d5d82 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +using namespace std; + +struct custom_formattable_type { + string_view string_content; +}; + +template <> +struct std::formatter { + basic_format_parse_context::iterator parse(basic_format_parse_context& parse_ctx) { + if (parse_ctx.begin() != parse_ctx.end()) { + throw format_error{"only empty specs please"}; + } + return parse_ctx.end(); + } + format_context::iterator format(const custom_formattable_type& val, format_context& ctx) { + ctx.advance_to(copy(val.string_content.begin(), val.string_content.end(), ctx.out())); + return ctx.out(); + } +}; + +constexpr void test_disabled_formatter_is_disabled() { + using F = formatter; + static_assert(!is_default_constructible_v); + static_assert(!is_copy_constructible_v); + static_assert(!is_move_constructible_v); + static_assert(!is_copy_assignable_v); + static_assert(!is_move_assignable_v); +} + +int main() { + assert(format("{}", custom_formattable_type{"f"}) == "f"s); + return 0; +} From 06a85f0c2ac60eaf09abb65897e02796dcdf11e8 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Tue, 23 Mar 2021 17:27:31 -0700 Subject: [PATCH 28/94] : Increase the buffer size for specful floats (#1766) Special thanks to @STL for helping define the limits of floating point numbers. The issue is basically that the buffer for specful floats was too small. You can specify an arbitrary precision for some formatting types, which means a lot more zeroes basically everywhere. If we only consider the most precision needed to represent a floating point number full, we need to consider the smallest double. It has an exponent of 2^-1074. As proven in the comment, each power of 2 adds an additional decimal digit, giving us a limit of 1074 decimal digits necessary to fully represent the smallest number. On top of this, we need the decimal point, the preceding 0, and a negative sign, bumping us to a total of 1077 characters. Any additional precision would only add 0s to the end of this number. Now consider the largest number. It is much easier to represent, clocking in at just 309 digits, none of which are decimal points. However, if we format it as a fixed point with 1074 precision, we would get 1074 zeroes at the end. To not worry about inspecting the value we format, I have chosen a large enough buffer to accomodate the largest number with the largest meaningful precision. Any precision past this is just appending 0s. Since only 'f' and '#g' care about the extra zeroes, the code change is pretty managable. --- stl/inc/format | 50 ++++++++++++++----- .../test.cpp | 24 +++++++++ 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 5be09ff73a4..3a7ab62f564 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1584,9 +1584,32 @@ _OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs< throw format_error("invalid floating point type"); } - array _Buffer; + // Consider the powers of 2 in decimal: + // 2^-1 = 0.5 + // 2^-2 = 0.25 + // 2^-3 = 0.125 + // 2^-4 = 0.0625 + // Each power of 2 consumes one more decimal digit. This is because: + // 2^-N * 5^-N = 10^-N + // 2^-N = 10^-N * 5^N + // Example: 2^-4 = 10^-4 * 5^4 = 0.0001 * 625 + // Therefore, the min subnormal 2^-1074 consumes 1074 digits of precision (digits after the decimal point). + // We need 3 more characters for a potential negative sign, the zero integer part, and the decimal point. + // Therefore, the precision can be clamped to 1074. + // The largest number consumes 309 digits before the decimal point. With a precision of 1074, and it being negative, + // it would use a buffer of size 1074+309+2. + // We need to add an additional number to the max exponent to accommodate the ones place. + constexpr auto _Max_precision = 1074; + constexpr auto _Buffer_size = _Max_precision + DBL_MAX_10_EXP + 3; + array _Buffer; to_chars_result _Result; + auto _Extra_precision = 0; + if (_Precision > _Max_precision) { + _Extra_precision = _Precision - _Max_precision; + _Precision = _Max_precision; + } + if (_Precision == -1) { _Result = _STD to_chars(_Buffer.data(), _Buffer.data() + _Buffer.size(), _Value, _Format); } else { @@ -1635,14 +1658,18 @@ _OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs< } if (_Specs._Type == 'g' || _Specs._Type == 'G') { - _Zeroes_to_append = _Precision - static_cast(_Exponent_start - _Buffer_start); + _Zeroes_to_append = _Extra_precision + _Precision - static_cast(_Exponent_start - _Buffer_start); if (!_Append_decimal) { _Zeroes_to_append += 1; } - _Width += _Zeroes_to_append; } } + if (_Is_finite && (_Specs._Type == 'f' || _Specs._Type == 'F')) { + _Zeroes_to_append = _Extra_precision; + } + _Width += _Zeroes_to_append; + const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Align::_None; auto _Writer = [&](_OutputIt _Out) { _Out = _Write_sign(_Out, _Sgn, _Is_negative); @@ -1651,16 +1678,15 @@ _OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs< _Out = _STD fill_n(_Out, _Specs._Width - _Width, '0'); } - if (_Specs._Alt) { - _Out = _STD copy(_Buffer_start, _Exponent_start, _Out); - _Buffer_start = _Exponent_start; + _Out = _STD copy(_Buffer_start, _Exponent_start, _Out); + _Buffer_start = _Exponent_start; - if (_Append_decimal) { - *_Out++ = '.'; - } - for (; _Zeroes_to_append > 0; --_Zeroes_to_append) { - *_Out++ = '0'; - } + if (_Specs._Alt && _Append_decimal) { + *_Out++ = '.'; + } + + for (; _Zeroes_to_append > 0; --_Zeroes_to_append) { + *_Out++ = '0'; } return _STD copy(_Buffer_start, _Result.ptr, _Out); diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index a380c2f8cda..99453548fec 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include #include #include #include @@ -632,6 +633,29 @@ void test_float_specs() { assert(format(STR("{:.4g}"), value) == STR("1235")); assert(format(STR("{:.4G}"), value) == STR("1235")); + assert(format(STR("{:.3000f}"), -numeric_limits::max()).size() + == 3002 + numeric_limits::max_exponent10 + 1); + assert(format(STR("{:.3000f}"), -numeric_limits::denorm_min()).size() == 3003); + assert(format(STR("{:#.3000g}"), -numeric_limits::max()).size() == 3002); + assert(format(STR("{:#.3000g}"), -numeric_limits::denorm_min()).size() + == 3007 - static_cast(is_same_v)); + + for (auto limits : {-numeric_limits::max(), -numeric_limits::denorm_min()}) { + auto fixed3000 = format(STR("{:.3000f}"), limits); + auto fixed1500 = format(STR("{:.1500f}"), limits); + assert(fixed1500 == fixed3000.substr(0, fixed1500.size())); + assert(all_of(fixed3000.begin() + static_cast(fixed1500.size()), fixed3000.end(), + [](auto ch) { return ch == charT{'0'}; })); + } + + for (auto limits : {-numeric_limits::max(), -numeric_limits::denorm_min()}) { + auto general3000 = format(STR("{:#.3000g}"), limits); + auto general1500 = format(STR("{:#.3000g}"), limits); + assert(general1500 == general3000.substr(0, general1500.size())); + assert(all_of(general3000.begin() + static_cast(general1500.size()), general3000.end(), + [](auto ch) { return ch == charT{'0'}; })); + } + // Leading zero assert(format(STR("{:06}"), Float{0}) == STR("000000")); assert(format(STR("{:06}"), Float{1.2}) == STR("0001.2")); From 510efc32912f6a0c1c154b676ffcc7b9fecc530f Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Wed, 24 Mar 2021 16:16:48 -0700 Subject: [PATCH 29/94] : Add localized specifier (#1767) * : Add localized specifier Adds the 'L' specifier to specful writing. This works by precounting how many "thousands" separaters are going to be necessary to write the whole part of the number being formatted. Then the number is written piecewise according to the grouping of the locale. This is the same for floating points and integers so the implementation is reused. The locale's grouping is a string(!) that specifies the number of digits per grouping. Each character is used once, except the last one which is reused as necessary. For example, in "en-US" the grouping is simply `\3`, as numbers are divided by 3s. In "hi-IN" the groupings are `\3\2`. This means that the hundreds are one group, then 2 digits are grouped after that (e.g. 10,20,300). Test are currently failing because the locale object exists in a different DLL than the headers being used (when the test matrix mixes up the `_ITERATOR_DEBUG_LEVEL`). This corrupts the stack and leads to bad things. The code is getting a little messy and probably overly complicated. Once all the formatting specific stuff is put it, it would be a good idea to go back and clean up the code. I don't want to do any major refactors currently with multiple PRs in flight and being feature incomplete, but once that's done I can look at it with fresh eyes. * Make tests work * Comments * Update tests/std/tests/P0645R10_text_formatting_formatting/test.cpp Remove extraneous `std::`-qualification Co-authored-by: Casey Carter --- stl/inc/format | 170 ++++++++++++++---- .../test.cpp | 76 ++++++++ 2 files changed, 214 insertions(+), 32 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 3a7ab62f564..1ed87e543c8 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1392,8 +1392,57 @@ _NODISCARD constexpr bool _In_bounds(const _Ty _Value) { } } +_NODISCARD inline int _Count_separators(size_t _Digits, const string_view _Groups) { + int _Separators = 0; + if (!_Groups.empty()) { + // Calculate the amount of separators that are going to be inserted based on the groupings of the locale. + auto _Group_it = _Groups.begin(); + while (_Digits > static_cast(*_Group_it)) { + _Digits -= static_cast(*_Group_it); + ++_Separators; + if (_Group_it + 1 != _Groups.end()) { + ++_Group_it; + } + } + } + return _Separators; +} + +template +_OutputIt _Write_separated_integer(const char* _Start, const char* const _End, const string_view _Groups, + const _CharT _Separator, int _Separators, _OutputIt _Out) { + auto _Group_it = _Groups.begin(); + auto _Repeats = 0; + auto _Grouped = 0; + + for (int _Section = 0; _Section < _Separators; ++_Section) { + _Grouped += *_Group_it; + if (_Group_it + 1 != _Groups.end()) { + ++_Group_it; + } else { + ++_Repeats; + } + } + _Out = _STD copy(_Start, _End - _Grouped, _Out); + _Start = _End - _Grouped; + + for (; _Separators > 0; --_Separators) { + if (_Repeats > 0) { + --_Repeats; + } else { + --_Group_it; + } + + *_Out++ = _Separator; + _Out = _STD copy(_Start, _Start + *_Group_it, _Out); + _Start += *_Group_it; + } + _STL_INTERNAL_CHECK(_Start == _End); + return _Out; +} + template -_OutputIt _Write(_OutputIt _Out, monostate, const _Basic_format_specs<_CharT>&) { +_OutputIt _Write(_OutputIt _Out, monostate, const _Basic_format_specs<_CharT>&, locale) { _STL_INTERNAL_CHECK(false); return _Out; } @@ -1401,13 +1450,13 @@ _OutputIt _Write(_OutputIt _Out, monostate, const _Basic_format_specs<_CharT>&) #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template -_OutputIt _Write_integral(_OutputIt _Out, const _Integral _Value, _Basic_format_specs<_CharT> _Specs) { +_OutputIt _Write_integral(_OutputIt _Out, const _Integral _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { if (_Specs._Type == 'c') { if (!_In_bounds<_CharT>(_Value)) { throw format_error("integral cannot be stored in charT"); } _Specs._Alt = false; - return _Write(_Out, static_cast<_CharT>(_Value), _Specs); + return _Write(_Out, static_cast<_CharT>(_Value), _Specs, _Locale); } if (_Specs._Precision != -1) { @@ -1471,6 +1520,15 @@ _OutputIt _Write_integral(_OutputIt _Out, const _Integral _Value, _Basic_format_ _Width += static_cast(_Prefix.size()); } + auto _Separators = 0; + string _Groups; + if (_Specs._Localized) { + _Groups = _STD use_facet>(_Locale).grouping(); + _Separators = _Count_separators(_End - _Buffer_start, _Groups); + // TRANSITION, separators may be wider for wide chars + _Width += _Separators; + } + const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Align::_None; auto _Writer = [&, _End = _End](_OutputIt _Out) { _Out = _Write_sign(_Out, _Specs._Sgn, _Value < _Integral{0}); @@ -1478,6 +1536,10 @@ _OutputIt _Write_integral(_OutputIt _Out, const _Integral _Value, _Basic_format_ if (_Write_leading_zeroes && _Width < _Specs._Width) { _Out = _STD fill_n(_Out, _Specs._Width - _Width, '0'); } + if (_Separators > 0) { + return _Write_separated_integer(_Buffer_start, _End, _Groups, + _STD use_facet>(_Locale).thousands_sep(), _Separators, _Out); + } return _STD copy(_Buffer_start, _End, _Out); }; @@ -1492,32 +1554,40 @@ _OutputIt _Write_integral(_OutputIt _Out, const _Integral _Value, _Basic_format_ // clang-format off template requires (!_CharT_or_bool<_Integral, _CharT>) -_OutputIt _Write(_OutputIt _Out, const _Integral _Value, const _Basic_format_specs<_CharT>& _Specs) { +_OutputIt _Write(_OutputIt _Out, const _Integral _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { // clang-format on - return _Write_integral(_Out, _Value, _Specs); + return _Write_integral(_Out, _Value, _Specs, _Locale); } template -_OutputIt _Write(_OutputIt _Out, const bool _Value, const _Basic_format_specs<_CharT>& _Specs) { +_OutputIt _Write(_OutputIt _Out, const bool _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { if (_Specs._Type != '\0' && _Specs._Type != 's') { - return _Write_integral(_Out, static_cast(_Value), _Specs); + return _Write_integral(_Out, static_cast(_Value), _Specs, _Locale); } if (_Specs._Precision != -1) { throw format_error("bool cannot have a precision"); } + if (_Specs._Localized) { + _Specs._Localized = false; + return _Write(_Out, + _Value ? static_cast>(_STD use_facet>(_Locale).truename()) + : static_cast>(_STD use_facet>(_Locale).falsename()), + _Specs, _Locale); + } + if constexpr (is_same_v<_CharT, wchar_t>) { - return _Write(_Out, _Value ? L"true" : L"false", _Specs); + return _Write(_Out, _Value ? L"true" : L"false", _Specs, _Locale); } else { - return _Write(_Out, _Value ? "true" : "false", _Specs); + return _Write(_Out, _Value ? "true" : "false", _Specs, _Locale); } } template -_OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs) { +_OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { if (_Specs._Type != '\0' && _Specs._Type != 'c') { - return _Write_integral(_Out, _Value, _Specs); + return _Write_integral(_Out, _Value, _Specs, _Locale); } if (_Specs._Precision != -1) { @@ -1526,13 +1596,13 @@ _OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT // Clear the type so that the string_view writer doesn't fail on 'c'. _Specs._Type = '\0'; - return _Write(_Out, basic_string_view<_CharT>{&_Value, 1}, _Specs); + return _Write(_Out, basic_string_view<_CharT>{&_Value, 1}, _Specs, _Locale); } #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template -_OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs<_CharT>& _Specs) { +_OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { auto _Sgn = _Specs._Sgn; if (_Sgn == _Sign::_None) { _Sgn = _Sign::_Minus; @@ -1641,10 +1711,13 @@ _OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs< auto _Append_decimal = false; auto _Exponent_start = _Result.ptr; + auto _Radix_point = _Result.ptr; + auto _Integral_end = _Result.ptr; auto _Zeroes_to_append = 0; + auto _Separators = 0; + string _Groups; - if (_Specs._Alt && _Is_finite) { - auto _Radix_point = _Result.ptr; + if ((_Specs._Alt || _Specs._Localized) && _Is_finite) { for (auto _It = _Buffer_start; _It < _Result.ptr; ++_It) { if (*_It == '.') { _Radix_point = _It; @@ -1652,32 +1725,56 @@ _OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs< _Exponent_start = _It; } } - if (_Radix_point == _Result.ptr) { - ++_Width; - _Append_decimal = true; - } + _Integral_end = (_STD min)(_Radix_point, _Exponent_start); - if (_Specs._Type == 'g' || _Specs._Type == 'G') { - _Zeroes_to_append = _Extra_precision + _Precision - static_cast(_Exponent_start - _Buffer_start); - if (!_Append_decimal) { - _Zeroes_to_append += 1; + if (_Specs._Alt) { + if (_Radix_point == _Result.ptr) { + // TRANSITION, decimal point may be wider + ++_Width; + _Append_decimal = true; + } + if (_Specs._Type == 'g' || _Specs._Type == 'G') { + _Zeroes_to_append = _Extra_precision + _Precision - static_cast(_Exponent_start - _Buffer_start); + if (!_Append_decimal) { + _Zeroes_to_append += 1; + } } } + + if (_Specs._Localized) { + _Groups = _STD use_facet>(_Locale).grouping(); + _Separators = _Count_separators(_Integral_end - _Buffer_start, _Groups); + } } if (_Is_finite && (_Specs._Type == 'f' || _Specs._Type == 'F')) { _Zeroes_to_append = _Extra_precision; } + _Width += _Zeroes_to_append; - const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Align::_None; - auto _Writer = [&](_OutputIt _Out) { + const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Align::_None && _Is_finite; + + auto _Writer = [&](_OutputIt _Out) { _Out = _Write_sign(_Out, _Sgn, _Is_negative); - if (_Write_leading_zeroes && _Width < _Specs._Width && _Is_finite) { + if (_Write_leading_zeroes && _Width < _Specs._Width) { _Out = _STD fill_n(_Out, _Specs._Width - _Width, '0'); } + if (_Specs._Localized) { + _Out = _Write_separated_integer(_Buffer_start, _Integral_end, _Groups, + _STD use_facet>(_Locale).thousands_sep(), _Separators, _Out); + if (_Radix_point != _Result.ptr || _Append_decimal) { + *_Out++ = _STD use_facet>(_Locale).decimal_point(); + _Append_decimal = false; + } + _Buffer_start = _Integral_end; + if (_Radix_point != _Result.ptr) { + ++_Buffer_start; + } + } + _Out = _STD copy(_Buffer_start, _Exponent_start, _Out); _Buffer_start = _Exponent_start; @@ -1692,7 +1789,7 @@ _OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs< return _STD copy(_Buffer_start, _Result.ptr, _Out); }; - if (_Write_leading_zeroes && _Is_finite) { + if (_Write_leading_zeroes) { return _Writer(_Out); } @@ -1701,7 +1798,7 @@ _OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs< #pragma warning(pop) template -_OutputIt _Write(_OutputIt _Out, const void* const _Value, const _Basic_format_specs<_CharT>& _Specs) { +_OutputIt _Write(_OutputIt _Out, const void* const _Value, const _Basic_format_specs<_CharT>& _Specs, locale) { if (_Specs._Type != '\0' && _Specs._Type != 'p') { throw format_error("invalid const void* type"); } @@ -1722,6 +1819,10 @@ _OutputIt _Write(_OutputIt _Out, const void* const _Value, const _Basic_format_s throw format_error("const void* cannot have a leading zero"); } + if (_Specs._Localized) { + throw format_error("const void* cannot be localized"); + } + // 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 hex number represents (log2(16) = log2(2^4) = 4). @@ -1738,12 +1839,13 @@ _OutputIt _Write(_OutputIt _Out, const void* const _Value, const _Basic_format_s } template -_OutputIt _Write(_OutputIt _Out, const _CharT* _Value, const _Basic_format_specs<_CharT>& _Specs) { - return _Write(_Out, basic_string_view<_CharT>{_Value}, _Specs); +_OutputIt _Write(_OutputIt _Out, const _CharT* _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { + return _Write(_Out, basic_string_view<_CharT>{_Value}, _Specs, _Locale); } template -_OutputIt _Write(_OutputIt _Out, const basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs) { +_OutputIt _Write( + _OutputIt _Out, const basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs, locale) { if (_Specs._Type != '\0' && _Specs._Type != 's') { throw format_error("invalid string type"); } @@ -1760,6 +1862,10 @@ _OutputIt _Write(_OutputIt _Out, const basic_string_view<_CharT> _Value, const _ throw format_error("string cannot have a leading zero"); } + if (_Specs._Localized) { + throw format_error("string cannot be localized"); + } + auto _Printed_size = static_cast(_Value.size()); if (_Specs._Precision != -1 && _Printed_size > _Specs._Precision) { @@ -1816,7 +1922,7 @@ struct _Arg_formatter { _OutputIt operator()(_Ty _Val) { _STL_INTERNAL_CHECK(_Specs); _STL_INTERNAL_CHECK(_Ctx); - return _Write(_Ctx->out(), _Val, *_Specs); + return _Write(_Ctx->out(), _Val, *_Specs, _Ctx->locale()); } }; diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 99453548fec..ffa31edaafb 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -35,6 +35,13 @@ struct choose_literal { #define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) #define STR(Str) TYPED_LITERAL(charT, Str) +// Test against IDL mismatch between the DLL which stores the locale and the code which uses it. +#ifdef _DEBUG +#define DEFAULT_IDL_SETTING 2 +#else +#define DEFAULT_IDL_SETTING 0 +#endif + template auto make_testing_format_args(Args&&... vals) { if constexpr (is_same_v) { @@ -438,6 +445,11 @@ void test_intergal_specs() { assert(format(STR("{:#X}"), integral{-255}) == STR("-0XFF")); } + if constexpr (is_same_v) { + assert(format(STR("{:b}"), numeric_limits::min()) + == STR("-1000000000000000000000000000000000000000000000000000000000000000")); + } + // Leading zero assert(format(STR("{:0}"), integral{0}) == STR("0")); assert(format(STR("{:03}"), integral{0}) == STR("000")); @@ -452,6 +464,26 @@ void test_intergal_specs() { // Precision throw_helper(STR("{:.1}"), integral{0}); + // Locale +#if !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING + assert(format(locale{"en-US"}, STR("{:L}"), integral{0}) == STR("0")); + assert(format(locale{"en-US"}, STR("{:L}"), integral{100}) == STR("100")); + assert(format(locale{"en-US"}, STR("{:L}"), integral{1'000}) == STR("1,000")); + assert(format(locale{"en-US"}, STR("{:L}"), integral{10'000}) == STR("10,000")); + assert(format(locale{"en-US"}, STR("{:L}"), integral{100'000}) == STR("100,000")); + assert(format(locale{"en-US"}, STR("{:L}"), integral{1'000'000}) == STR("1,000,000")); + assert(format(locale{"en-US"}, STR("{:L}"), integral{10'000'000}) == STR("10,000,000")); + assert(format(locale{"en-US"}, STR("{:L}"), integral{100'000'000}) == STR("100,000,000")); + + assert(format(locale{"en-US"}, STR("{:Lx}"), integral{0x123'abc}) == STR("123,abc")); + assert(format(locale{"en-US"}, STR("{:6L}"), integral{1'000}) == STR(" 1,000")); + + assert(format(locale{"hi-IN"}, STR("{:L}"), integral{10'000'000}) == STR("1,00,00,000")); + assert(format(locale{"hi-IN"}, STR("{:L}"), integral{100'000'000}) == STR("10,00,00,000")); + + assert(format(locale{"hi-IN"}, STR("{:Lx}"), integral{0x123'abc}) == STR("1,23,abc")); +#endif // !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING + // Type assert(format(STR("{:b}"), integral{0}) == STR("0")); assert(format(STR("{:b}"), integral{100}) == STR("1100100")); @@ -492,6 +524,29 @@ void test_bool_specs() { // Precision throw_helper(STR("{:.5}"), true); + // Locale + assert(format(STR("{:L}"), true) == STR("true")); + assert(format(STR("{:L}"), false) == STR("false")); +#if !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING + assert(format(locale{"en-US"}, STR("{:L}"), true) == STR("true")); + assert(format(locale{"en-US"}, STR("{:L}"), false) == STR("false")); + + struct my_bool : numpunct { + virtual basic_string do_truename() const { + return STR("yes"); + } + + virtual basic_string do_falsename() const { + return STR("no"); + } + }; + + locale loc{locale::classic(), new my_bool}; + + assert(format(loc, STR("{:L}"), true) == STR("yes")); + assert(format(loc, STR("{:L}"), false) == STR("no")); +#endif // !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING + // Type assert(format(STR("{:s}"), true) == STR("true")); throw_helper(STR("{:a}"), true); @@ -662,6 +717,20 @@ void test_float_specs() { assert(format(STR("{:06}"), nan) == STR(" nan")); assert(format(STR("{:06}"), inf) == STR(" inf")); + // Locale +#if !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING + assert(format(locale{"en-US"}, STR("{:L}"), Float{0}) == STR("0")); + assert(format(locale{"en-US"}, STR("{:Lf}"), Float{0}) == STR("0.000000")); + assert(format(locale{"en-US"}, STR("{:L}"), Float{100}) == STR("100")); + assert(format(locale{"en-US"}, STR("{:L}"), Float{100.2345}) == STR("100.2345")); + assert(format(locale{"en-US"}, STR("{:.4Lf}"), value) == STR("1,234.5273")); + assert(format(locale{"en-US"}, STR("{:#.4Lg}"), Float{0}) == STR("0.000")); + assert(format(locale{"en-US"}, STR("{:L}"), nan) == STR("nan")); + assert(format(locale{"en-US"}, STR("{:L}"), inf) == STR("inf")); + + assert(format(locale{"de_DE"}, STR("{:Lf}"), Float{0}) == STR("0,000000")); +#endif // !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING + // Type assert(format(STR("{:a}"), value) == STR("1.34a1cp+10")); assert(format(STR("{:A}"), value) == STR("1.34A1CP+10")); @@ -697,6 +766,9 @@ void test_pointer_specs() { // Precision throw_helper(STR("{:.5}"), nullptr); + // Locale + throw_helper(STR("{:L}"), nullptr); + // Types assert(format(STR("{:p}"), nullptr) == STR("0x0")); throw_helper(STR("{:a}"), nullptr); @@ -742,6 +814,10 @@ void test_string_specs() { assert(format(STR("{:5.2}"), view) == STR("sc ")); assert(format(STR("{:>5.2}"), view) == STR(" sc")); + // Locale + throw_helper(STR("{:L}"), cstr); + throw_helper(STR("{:L}"), view); + // Types assert(format(STR("{:s}"), cstr) == cstr); throw_helper(STR("{:a}"), cstr); From 48864c8619be4b76dd948280ae6ffe78961a4837 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Wed, 31 Mar 2021 11:34:10 -0700 Subject: [PATCH 30/94] : Add format_to_n and formatted_size (#1790) * : Add format_to_n and formatted_size Similar methods were used to implement both features: by creating our own output iterators that either count characters or only insert up until a certain character count. The output iterators were modelled after back_insert_iterator. There can definetly be more tests, I just wanted the methodology to be reviewed first and then testing can be added a bit later. * Address comments * Simplify iterators --- stl/inc/format | 147 +++++++++++++++++- .../test.cpp | 43 +++++ 2 files changed, 182 insertions(+), 8 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 1ed87e543c8..f8660c9ee0c 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1356,21 +1356,21 @@ _NODISCARD constexpr string_view _Get_integral_prefix(const char _Type, const _I template _OutputIt _Write_sign(_OutputIt _Out, const _Sign _Sgn, const bool _Is_negative) { if (_Is_negative) { - *_Out = '-'; + *_Out++ = '-'; } else { switch (_Sgn) { case _Sign::_Plus: - *_Out = '+'; + *_Out++ = '+'; break; case _Sign::_Space: - *_Out = ' '; + *_Out++ = ' '; break; case _Sign::_None: case _Sign::_Minus: break; } } - return ++_Out; + return _Out; } inline void _Buffer_to_uppercase(char* _Begin, const char* _End) { @@ -1998,7 +1998,7 @@ auto make_wformat_args(const _Args&... _Vals) { template using format_args_t = basic_format_args>; -template +template _OutputIt> _OutputIt vformat_to(_OutputIt _Out, string_view _Fmt, format_args_t, char> _Args) { _Format_handler<_OutputIt, char, basic_format_context<_OutputIt, char>> _Handler( _Out, _Fmt, _Args, locale::classic()); @@ -2006,7 +2006,7 @@ _OutputIt vformat_to(_OutputIt _Out, string_view _Fmt, format_args_t +template _OutputIt> _OutputIt vformat_to(_OutputIt _Out, wstring_view _Fmt, format_args_t, wchar_t> _Args) { _Format_handler<_OutputIt, wchar_t, basic_format_context<_OutputIt, wchar_t>> _Handler( _Out, _Fmt, _Args, locale::classic()); @@ -2014,7 +2014,7 @@ _OutputIt vformat_to(_OutputIt _Out, wstring_view _Fmt, format_args_t +template _OutputIt> _OutputIt vformat_to( _OutputIt _Out, const locale& _Loc, string_view _Fmt, format_args_t, char> _Args) { _Format_handler<_OutputIt, char, basic_format_context<_OutputIt, char>> _Handler(_Out, _Fmt, _Args, _Loc); @@ -2022,7 +2022,7 @@ _OutputIt vformat_to( return _Handler._Ctx.out(); } -template +template _OutputIt> _OutputIt vformat_to( _OutputIt _Out, const locale& _Loc, wstring_view _Fmt, format_args_t, wchar_t> _Args) { _Format_handler<_OutputIt, wchar_t, basic_format_context<_OutputIt, wchar_t>> _Handler(_Out, _Fmt, _Args, _Loc); @@ -2074,6 +2074,137 @@ wstring format(const locale& _Loc, wstring_view _Fmt, const _Types&... _Args) { return _STD vformat(_Loc, _Fmt, _STD make_wformat_args(_Args...)); } +template +struct _Format_to_n_iterator { + _OutputIt _Out; + iter_difference_t<_OutputIt> _Max; + iter_difference_t<_OutputIt> _Count = 0; + + using difference_type = iter_difference_t<_OutputIt>; + + _Format_to_n_iterator& operator=(_CharT _Ch) { + if (_Count < _Max) { + *_Out++ = _Ch; + } + ++_Count; + return *this; + } + + _Format_to_n_iterator& operator*() { + return *this; + } + + _Format_to_n_iterator& operator++() { + return *this; + } + + _Format_to_n_iterator& operator++(int) { + return *this; + } +}; + +template +struct format_to_n_result { + _OutputIt out; + iter_difference_t<_OutputIt> size; +}; + +template _OutputIt, class... _Types> +format_to_n_result<_OutputIt> format_to_n( + _OutputIt _Out, const iter_difference_t<_OutputIt> _Max, const string_view _Fmt, const _Types&... _Args) { + _Format_to_n_iterator<_OutputIt, char> _It{._Out = _STD move(_Out), ._Max = _Max}; + + using _Context = basic_format_context; + auto _Arg_store = _STD make_format_args<_Context>(_Args...); + _It = _STD vformat_to(_STD move(_It), _Fmt, _STD move(_Arg_store)); + return {.out = _STD move(_It._Out), .size = _It._Count}; +} + +template _OutputIt, class... _Types> +format_to_n_result<_OutputIt> format_to_n( + _OutputIt _Out, const iter_difference_t<_OutputIt> _Max, const wstring_view _Fmt, const _Types&... _Args) { + _Format_to_n_iterator<_OutputIt, wchar_t> _It{._Out = _STD move(_Out), ._Max = _Max}; + + using _Context = basic_format_context; + auto _Arg_store = _STD make_format_args<_Context>(_Args...); + _It = _STD vformat_to(_STD move(_It), _Fmt, _STD move(_Arg_store)); + return {.out = _STD move(_It._Out), .size = _It._Count}; +} + +template _OutputIt, class... _Types> +format_to_n_result<_OutputIt> format_to_n(_OutputIt _Out, const iter_difference_t<_OutputIt> _Max, const locale& _Loc, + const string_view _Fmt, const _Types&... _Args) { + _Format_to_n_iterator<_OutputIt, char> _It{._Out = _STD move(_Out), ._Max = _Max}; + + using _Context = basic_format_context; + auto _Arg_store = _STD make_format_args<_Context>(_Args...); + _It = _STD vformat_to(_STD move(_It), _Loc, _Fmt, _STD move(_Arg_store)); + return {.out = _STD move(_It._Out), .size = _It._Count}; +} + +template _OutputIt, class... _Types> +format_to_n_result<_OutputIt> format_to_n(_OutputIt _Out, const iter_difference_t<_OutputIt> _Max, const locale& _Loc, + const wstring_view _Fmt, const _Types&... _Args) { + _Format_to_n_iterator<_OutputIt, wchar_t> _It{._Out = _STD move(_Out), ._Max = _Max}; + + using _Context = basic_format_context; + auto _Arg_store = _STD make_format_args<_Context>(_Args...); + _It = _STD vformat_to(_STD move(_It), _Loc, _Fmt, _STD move(_Arg_store)); + return {.out = _STD move(_It._Out), .size = _It._Count}; +} + +struct _Counting_iterator { + size_t _Count = 0; + + using difference_type = ptrdiff_t; + + template + _Counting_iterator& operator=(_CharT) { + ++_Count; + return *this; + } + + _Counting_iterator& operator*() { + return *this; + } + + _Counting_iterator& operator++() { + return *this; + } + + _Counting_iterator& operator++(int) { + return *this; + } +}; + +template +size_t formatted_size(const string_view _Fmt, const _Types&... _Args) { + using _Context = basic_format_context<_Counting_iterator, char>; + auto _Arg_store = _STD make_format_args<_Context>(_Args...); + return _STD vformat_to(_Counting_iterator{}, _Fmt, _STD move(_Arg_store))._Count; +} + +template +size_t formatted_size(const wstring_view _Fmt, const _Types&... _Args) { + using _Context = basic_format_context<_Counting_iterator, wchar_t>; + auto _Arg_store = _STD make_format_args<_Context>(_Args...); + return _STD vformat_to(_Counting_iterator{}, _Fmt, _STD move(_Arg_store))._Count; +} + +template +size_t formatted_size(const locale& _Loc, const string_view _Fmt, const _Types&... _Args) { + using _Context = basic_format_context<_Counting_iterator, char>; + auto _Arg_store = _STD make_format_args<_Context>(_Args...); + return _STD vformat_to(_Counting_iterator{}, _Loc, _Fmt, _STD move(_Arg_store))._Count; +} + +template +size_t formatted_size(const locale& _Loc, const wstring_view _Fmt, const _Types&... _Args) { + using _Context = basic_format_context<_Counting_iterator, wchar_t>; + auto _Arg_store = _STD make_format_args<_Context>(_Args...); + return _STD vformat_to(_Counting_iterator{}, _Loc, _Fmt, _STD move(_Arg_store))._Count; +} + _STD_END #pragma pop_macro("new") diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index ffa31edaafb..3c1fbf01676 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -841,6 +841,46 @@ void test_spec_replacement_field() { test_string_specs(); } +template +void test_size_helper(const size_t expected_size, const basic_string_view fmt, const Args&... args) { + assert(formatted_size(fmt, args...) == expected_size); + assert(formatted_size(locale::classic(), fmt, args...) == expected_size); + + const auto signed_size = static_cast(expected_size); + basic_string str; + { + str.resize(expected_size); + const auto res = format_to_n(str.begin(), signed_size, fmt, args...); + assert(res.size == signed_size); + assert(res.out - str.begin() == signed_size); + assert(res.out == str.end()); + assert(format(fmt, args...) == str); + + basic_string locale_str; + locale_str.resize(expected_size); + format_to_n(locale_str.begin(), signed_size, locale::classic(), fmt, args...); + assert(str == locale_str); + assert(locale_str.size() == expected_size); + } + basic_string half_str; + { + const auto half_size = expected_size / 2; + half_str.resize(half_size); + const auto res = format_to_n(half_str.begin(), static_cast(half_size), fmt, args...); + assert(res.size == signed_size); + assert(static_cast(res.out - half_str.begin()) == half_size); + assert(res.out == half_str.end()); + } + assert(str.starts_with(half_str)); +} + +template +void test_size() { + test_size_helper(3, STR("{}"), 123); + test_size_helper(6, STR("{}"), 3.1415); + test_size_helper(8, STR("{:8}"), STR("scully")); +} + int main() { test_simple_formatting(); test_simple_formatting(); @@ -860,5 +900,8 @@ int main() { test_spec_replacement_field(); test_spec_replacement_field(); + test_size(); + test_size(); + return 0; } From a78b9aab0e6e6d446979a59690a195fea130e8b0 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Wed, 31 Mar 2021 12:27:48 -0700 Subject: [PATCH 31/94] : Add format_to (#1793) * : Add format_to Mostly a passthrough function, however it is moving the iterator instead of copying it so this will gel better with the move-only iterator PR I have planned. * Protecc with _STD --- stl/inc/format | 24 +++++++++++++++++++ .../test.cpp | 15 ++++++++++++ 2 files changed, 39 insertions(+) diff --git a/stl/inc/format b/stl/inc/format index f8660c9ee0c..60432ce69af 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -2030,6 +2030,30 @@ _OutputIt vformat_to( return _Handler._Ctx.out(); } +template +_OutputIt format_to(_OutputIt _Out, string_view _Fmt, const _Types&... _Args) { + using _Context = basic_format_context<_OutputIt, char>; + return _STD vformat_to(_STD move(_Out), _Fmt, _STD make_format_args<_Context>(_Args...)); +} + +template +_OutputIt format_to(_OutputIt _Out, wstring_view _Fmt, const _Types&... _Args) { + using _Context = basic_format_context<_OutputIt, wchar_t>; + return _STD vformat_to(_STD move(_Out), _Fmt, _STD make_format_args<_Context>(_Args...)); +} + +template +_OutputIt format_to(_OutputIt _Out, const locale& _Loc, string_view _Fmt, const _Types&... _Args) { + using _Context = basic_format_context<_OutputIt, char>; + return _STD vformat_to(_STD move(_Out), _Loc, _Fmt, _STD make_format_args<_Context>(_Args...)); +} + +template +_OutputIt format_to(_OutputIt _Out, const locale& _Loc, wstring_view _Fmt, const _Types&... _Args) { + using _Context = basic_format_context<_OutputIt, wchar_t>; + return _STD vformat_to(_STD move(_Out), _Loc, _Fmt, _STD make_format_args<_Context>(_Args...)); +} + inline string vformat(string_view _Fmt, format_args _Args) { string _Str; _STD vformat_to(_STD back_inserter(_Str), _Fmt, _Args); diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 3c1fbf01676..766f4f47c7b 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -74,6 +74,14 @@ void test_simple_formatting() { back_insert_iterator{output_string}, locale::classic(), STR("format"), make_testing_format_args()); assert(output_string == STR("format")); + output_string.clear(); + format_to(back_insert_iterator{output_string}, STR("format")); + assert(output_string == STR("format")); + + output_string.clear(); + format_to(back_insert_iterator{output_string}, locale::classic(), STR("format")); + assert(output_string == STR("format")); + assert(format(STR("f")) == STR("f")); assert(format(STR("format")) == STR("format")); assert(format(locale::classic(), STR("f")) == STR("f")); @@ -142,6 +150,13 @@ void test_simple_replacement_field() { back_insert_iterator{output_string}, locale::classic(), STR("{}"), make_testing_format_args(STR("f"))); assert(output_string == STR("f")); + output_string.clear(); + format_to(back_insert_iterator{output_string}, STR("{}"), STR("f")); + assert(output_string == STR("f")); + + output_string.clear(); + format_to(back_insert_iterator{output_string}, locale::classic(), STR("{}"), STR("f")); + assert(output_string == STR("f")); assert(format(STR("{}"), STR("f")) == STR("f")); assert(format(locale::classic(), STR("{}"), STR("f")) == STR("f")); From e500c6adc71c121d94b96b4e2440c187e46625d7 Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Thu, 1 Apr 2021 12:35:33 -0700 Subject: [PATCH 32/94] : Standard formatter specializations (#1782) * add _Dynamic_specs_handler * add _Format_arg_storage_type_direct * add most of formatter::format. * add format to formatter specilization. * change _Store and _Get_format_arg_storage_type to more closely reflect the standard * add all formatter specializations * fix formatting_args tests to test things the standard says. * add more extensive testing for custom formatter instantiations. Co-authored-by: Casey Carter --- stl/inc/format | 210 ++++++++++++++---- .../P0645R10_text_formatting_args/test.cpp | 28 +-- .../test.cpp | 114 +++++++++- 3 files changed, 279 insertions(+), 73 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 60432ce69af..390d6af5116 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -100,6 +100,9 @@ concept _Parse_replacement_field_callbacks = requires(_Ty _At, const _CharT* _Be template concept _CharT_or_bool = same_as<_Ty, _CharT> || same_as<_Ty, bool>; +template +concept _Format_supported_charT = _Is_any_of_v<_CharT, char, wchar_t>; + // clang-format on template @@ -125,6 +128,12 @@ public: _NODISCARD constexpr const_iterator end() const noexcept { return _Format_string.end(); } + _NODISCARD constexpr const _CharT* _Unchecked_begin() const noexcept { + return _Format_string._Unchecked_begin(); + } + _NODISCARD constexpr const _CharT* _Unchecked_end() const noexcept { + return _Format_string._Unchecked_end(); + } constexpr void advance_to(const const_iterator _It) { _Adl_verify_range(_It, _Format_string.end()); // _It must be after _Format_string.begin(). @@ -651,6 +660,15 @@ struct _Basic_format_specs { _CharT _Fill[4] = {' ', _CharT{0}, _CharT{0}, _CharT{0}}; }; +// Adds width and precision references to _Basic_format_specs. +// this is required for std::formatter implementations because we must +// parse the format specs without having access to the format args (via a format context) +template +struct _Dynamic_format_specs : _Basic_format_specs<_CharT> { + int _Dynamic_width_index = -1; + int _Dynamic_precision_index = -1; +}; + // Model of _Parse_specs_callbacks that fills a _Basic_format_specs with the parsed data. template class _Specs_setter { @@ -756,6 +774,19 @@ public: } }; +// Fetch the value of an argument associated with a dynamic +// width or precision specifier. This will be called with either +// _Width_checker or _Precision_checker as "_Handler". +template +_NODISCARD constexpr int _Get_dynamic_specs(const _FormatArg _Arg) { + _STL_INTERNAL_STATIC_ASSERT(_Is_any_of_v<_Handler, _Width_checker, _Precision_checker>); + const unsigned long long _Val = _STD visit_format_arg(_Handler(), _Arg); + if (_Val > (numeric_limits::max)()) { + throw format_error("Number is too big."); + } + return static_cast(_Val); +} + // Parses standard format specs into a _Basic_format_specs using _Specs_setter, and, // in addition handles dynamic width and precision. This is separate from _Specs setter // because it needs to know about the current basic_format_parse_context and basic_format_context @@ -769,12 +800,12 @@ public: : _Specs_setter<_CharT>(_Specs_), _Parse_ctx(_Parse_ctx_), _Ctx(_Ctx_) {} template - constexpr void _On_dynamic_width(_Id _Arg_id) { + constexpr void _On_dynamic_width(const _Id _Arg_id) { this->_Specs._Width = _Get_dynamic_specs<_Width_checker>(_Get_arg(_Arg_id)); } template - constexpr void _On_dynamic_precision(_Id _Arg_id) { + constexpr void _On_dynamic_precision(const _Id _Arg_id) { this->_Specs._Precision = _Get_dynamic_specs<_Precision_checker>(_Get_arg(_Arg_id)); } @@ -786,21 +817,46 @@ private: return _STD _Get_arg(_Ctx, _Parse_ctx.next_arg_id()); } - constexpr basic_format_arg<_Context> _Get_arg(size_t _Arg_id) { + constexpr basic_format_arg<_Context> _Get_arg(const size_t _Arg_id) { _Parse_ctx.check_arg_id(_Arg_id); return _STD _Get_arg(_Ctx, _Arg_id); } +}; + +template +class _Dynamic_specs_handler : public _Specs_setter { +public: + using _CharT = typename _ParseContext::char_type; + + constexpr _Dynamic_specs_handler(_Dynamic_format_specs<_CharT>& _Specs_, _ParseContext& _Parse_ctx_) + : _Specs_setter<_CharT>(_Specs_), _Dynamic_specs(_Specs_), _Parse_ctx(_Parse_ctx_) {} + + constexpr void _On_dynamic_width(const size_t _Arg_id) { + _Parse_ctx.check_arg_id(_Arg_id); + _Dynamic_specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Arg_id); + } + + constexpr void _On_dynamic_width(const _Auto_id_tag) { + _Dynamic_specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Parse_ctx.next_arg_id()); + } + + constexpr void _On_dynamic_precision(const size_t _Arg_id) { + _Parse_ctx.check_arg_id(_Arg_id); + _Dynamic_specs._Dynamic_precision_index = _Verify_dynamic_arg_index_in_range(_Arg_id); + } + constexpr void _On_dynamic_precision(const _Auto_id_tag) { + _Dynamic_specs._Dynamic_precision_index = _Verify_dynamic_arg_index_in_range(_Parse_ctx.next_arg_id()); + } + +private: + _Dynamic_format_specs<_CharT>& _Dynamic_specs; + _ParseContext& _Parse_ctx; - // Fetch the value of an argument associated with a dynamic - // width or precision specifier. This will be called with either - // _Width_checker or _Precision_checker as "_Handler". - template - static constexpr int _Get_dynamic_specs(_FormatArg _Arg) { - unsigned long long _Val = _STD visit_format_arg(_Handler(), _Arg); - if (_Val > (numeric_limits::max)()) { - throw format_error("Number is too big."); + _NODISCARD static constexpr int _Verify_dynamic_arg_index_in_range(const size_t _Idx) { + if (_Idx > static_cast((numeric_limits::max)())) { + throw format_error("Dynamic width or precision index too large."); } - return static_cast(_Val); + return static_cast(_Idx); } }; @@ -883,15 +939,7 @@ constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type) // clang-format off template requires _Has_formatter<_Context, _Ty> -/* consteval */ constexpr auto _Get_format_arg_storage_type(const _Ty& _Val) noexcept { - // clang-format on - return typename basic_format_arg<_Context>::handle{_Val}; -} - -// clang-format off -template - requires integral<_Ty> || floating_point<_Ty> -/* consteval */ constexpr auto _Get_format_arg_storage_type(_Ty) noexcept { +/* consteval */ constexpr auto _Get_format_arg_storage_type(const _Ty&) noexcept { // clang-format on using _Char_type = typename _Context::char_type; if constexpr (is_same_v<_Ty, monostate>) { @@ -917,8 +965,7 @@ template } else if constexpr (is_same_v<_Ty, long double>) { return static_cast(42); } else { - static_assert(_Always_false<_Ty>, "Invalid type passed to _Get_format_arg_storage_type"); - return static_cast(-1); + return typename basic_format_arg<_Context>::handle{42}; } } @@ -1003,15 +1050,6 @@ private: template requires _Has_formatter<_Context, _Ty> void _Store(const size_t _Arg_index, const _Ty& _Val) noexcept { - // clang-format on - using _Handle_type = typename basic_format_arg<_Context>::handle; - _Store_impl<_Handle_type>(_Arg_index, _Basic_format_arg_type::_Custom_type, _Handle_type{_Val}); - } - - // clang-format off - template - requires integral<_Ty> || floating_point<_Ty> - void _Store(const size_t _Arg_index, _Ty _Val) noexcept { // clang-format on if constexpr (is_same_v<_Ty, bool>) { _Store_impl(_Arg_index, _Basic_format_arg_type::_Bool_type, _Val); @@ -1035,7 +1073,8 @@ private: } else if constexpr (is_same_v<_Ty, long double>) { _Store_impl(_Arg_index, _Basic_format_arg_type::_Long_double_type, _Val); } else { - static_assert(_Always_false<_Ty>, "Invalid type passed to _Format_arg_store::_Store"); + using _Handle_type = typename basic_format_arg<_Context>::handle; + _Store_impl<_Handle_type>(_Arg_index, _Basic_format_arg_type::_Custom_type, _Handle_type{_Val}); } } @@ -1906,15 +1945,12 @@ template struct _Arg_formatter { using _Context = basic_format_context<_OutputIt, _CharT>; - _Context* _Ctx = nullptr; - _Basic_format_specs<_CharT>* _Specs = nullptr; - basic_format_parse_context<_CharT>* _Parse_ctx = nullptr; + _Context* _Ctx = nullptr; + _Basic_format_specs<_CharT>* _Specs = nullptr; - _OutputIt operator()(typename basic_format_arg<_Context>::handle _Handle) { - _STL_ASSERT(false, "The custom handler should be structurally unreachable for _Arg_formatter"); - _STL_INTERNAL_CHECK(_Parse_ctx); + _OutputIt operator()(typename basic_format_arg<_Context>::handle) { + _STL_VERIFY(false, "The custom handler should be structurally unreachable for _Arg_formatter"); _STL_INTERNAL_CHECK(_Ctx); - _Handle.format(*_Parse_ctx, *_Ctx); return _Ctx->out(); } @@ -1952,6 +1988,10 @@ struct _Format_handler { const _CharT* _On_format_specs(size_t _Id, const _CharT* _Begin, const _CharT* _End) { _Parse_context.advance_to(_Parse_context.begin() + (_Begin - &*_Parse_context.begin())); auto _Arg = _Ctx.arg(_Id); + if (_Arg._Active_state == _Basic_format_arg_type::_Custom_type) { + _Arg._Custom_state.format(_Parse_context, _Ctx); + return _Parse_context.begin()._Unwrapped(); + } _Basic_format_specs<_CharT> _Specs; _Specs_checker<_Specs_handler, _Context>> _Handler( _Specs_handler, _Context>(_Specs, _Parse_context, _Ctx), @@ -1961,12 +2001,7 @@ struct _Format_handler { throw format_error("Missing '}' in format string."); } _Ctx.advance_to(_STD visit_format_arg( - _Arg_formatter<_OutputIt, _CharT>{ - ._Ctx = _STD addressof(_Ctx), - ._Specs = _STD addressof(_Specs), - ._Parse_ctx = _STD addressof(_Parse_context), - }, - _Arg)); + _Arg_formatter<_OutputIt, _CharT>{._Ctx = _STD addressof(_Ctx), ._Specs = _STD addressof(_Specs)}, _Arg)); return _Begin; } }; @@ -1980,6 +2015,89 @@ struct formatter { formatter operator=(const formatter&) = delete; }; +template +struct _Formatter_base { + using _Pc = basic_format_parse_context<_CharT>; + + typename _Pc::iterator parse(_Pc& _ParseCtx) { + _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."); + } + return _ParseCtx.begin() + (_It - _ParseCtx._Unchecked_begin()); + } + + template + typename _FormatContext::iterator format(const _Ty& _Val, _FormatContext& _FormatCtx) { + if (_Specs._Dynamic_width_index >= 0) { + _Specs._Width = + _Get_dynamic_specs<_Width_checker>(_FormatCtx.arg(static_cast(_Specs._Dynamic_width_index))); + } + if (_Specs._Dynamic_precision_index >= 0) { + _Specs._Precision = _Get_dynamic_specs<_Precision_checker>( + _FormatCtx.arg(static_cast(_Specs._Dynamic_precision_index))); + } + return _STD visit_format_arg( + _Arg_formatter{ + ._Ctx = _STD addressof(_FormatCtx), ._Specs = _STD addressof(_Specs)}, + basic_format_arg<_FormatContext>{_Val}); + } + +private: + _Dynamic_format_specs<_CharT> _Specs; +}; + +#define _FORMAT_SPECIALIZE_FOR(_Type, _ArgType) \ + template <_Format_supported_charT _CharT> \ + struct formatter<_Type, _CharT> : _Formatter_base<_Type, _CharT, _ArgType> {} + +_FORMAT_SPECIALIZE_FOR(int, _Basic_format_arg_type::_Int_type); +_FORMAT_SPECIALIZE_FOR(unsigned int, _Basic_format_arg_type::_UInt_type); +_FORMAT_SPECIALIZE_FOR(long long, _Basic_format_arg_type::_Long_long_type); +_FORMAT_SPECIALIZE_FOR(unsigned long long, _Basic_format_arg_type::_ULong_long_type); +_FORMAT_SPECIALIZE_FOR(bool, _Basic_format_arg_type::_Bool_type); +_FORMAT_SPECIALIZE_FOR(float, _Basic_format_arg_type::_Float_type); +_FORMAT_SPECIALIZE_FOR(double, _Basic_format_arg_type::_Double_type); +_FORMAT_SPECIALIZE_FOR(long double, _Basic_format_arg_type::_Long_double_type); +_FORMAT_SPECIALIZE_FOR(nullptr_t, _Basic_format_arg_type::_Pointer_type); +_FORMAT_SPECIALIZE_FOR(void*, _Basic_format_arg_type::_Pointer_type); +_FORMAT_SPECIALIZE_FOR(const void*, _Basic_format_arg_type::_Pointer_type); +_FORMAT_SPECIALIZE_FOR(short, _Basic_format_arg_type::_Int_type); +_FORMAT_SPECIALIZE_FOR(unsigned short, _Basic_format_arg_type::_UInt_type); +_FORMAT_SPECIALIZE_FOR(long, _Basic_format_arg_type::_Int_type); +_FORMAT_SPECIALIZE_FOR(unsigned long, _Basic_format_arg_type::_UInt_type); +_FORMAT_SPECIALIZE_FOR(char, _Basic_format_arg_type::_Char_type); +_FORMAT_SPECIALIZE_FOR(signed char, _Basic_format_arg_type::_Int_type); +_FORMAT_SPECIALIZE_FOR(unsigned char, _Basic_format_arg_type::_UInt_type); + +#undef _FORMAT_SPECIALIZE_FOR + +// not using the macro because we'd like to avoid the formatter specialization +template <> +struct formatter : _Formatter_base {}; + +// We could use the macro for these specializations, but it's confusing to refer to symbols that are defined inside the +// macro in the macro's "call". +template <_Format_supported_charT _CharT> +struct formatter<_CharT*, _CharT> : _Formatter_base<_CharT*, _CharT, _Basic_format_arg_type::_CString_type> {}; + +template <_Format_supported_charT _CharT> +struct formatter + : _Formatter_base {}; + +template <_Format_supported_charT _CharT, size_t _Nty> +struct formatter + : _Formatter_base {}; + +template <_Format_supported_charT _CharT, class _Traits, class _Allocator> +struct formatter, _CharT> + : _Formatter_base, _CharT, _Basic_format_arg_type::_String_type> {}; + +template <_Format_supported_charT _CharT, class _Traits> +struct formatter, _CharT> + : _Formatter_base, _CharT, _Basic_format_arg_type::_String_type> {}; + using format_context = basic_format_context, string::value_type>; using wformat_context = basic_format_context, wstring::value_type>; using format_args = basic_format_args; diff --git a/tests/std/tests/P0645R10_text_formatting_args/test.cpp b/tests/std/tests/P0645R10_text_formatting_args/test.cpp index 08355b8a90f..e95ba049f9c 100644 --- a/tests/std/tests/P0645R10_text_formatting_args/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_args/test.cpp @@ -31,16 +31,6 @@ constexpr auto get_input_sv() { } } -class context { -public: - using char_type = char; -}; - -class wcontext { -public: - using char_type = wchar_t; -}; - enum class Arg_type : uint8_t { none, int_type, @@ -160,19 +150,11 @@ void test_format_arg_store() { test_empty_format_arg(); test_single_format_arg(42); - if constexpr (is_same_v) { - test_single_format_arg(42); - } else { + if constexpr (is_same_v) { test_single_format_arg(42); } test_single_format_arg(42); test_single_format_arg(42); -#ifdef __cpp_char8_t - test_single_format_arg(42); -#endif // __cpp_char8_t - test_single_format_arg(42); - test_single_format_arg(42); - test_single_format_arg(42); test_single_format_arg(42); test_single_format_arg(42); @@ -232,8 +214,8 @@ void test_format_arg_store() { } int main() { - test_basic_format_arg(); - test_basic_format_arg(); - test_format_arg_store(); - test_format_arg_store(); + test_basic_format_arg(); + test_basic_format_arg(); + test_format_arg_store(); + test_format_arg_store(); } diff --git a/tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp index 8fa5a1d5d82..211956f947c 100644 --- a/tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp @@ -9,26 +9,61 @@ #include using namespace std; -struct custom_formattable_type { +// copied from the string_view tests +template +struct choose_literal; // not defined + +template <> +struct choose_literal { + static constexpr const char* choose(const char* s, const wchar_t*) { + return s; + } +}; + +template <> +struct choose_literal { + static constexpr const wchar_t* choose(const char*, const wchar_t* s) { + return s; + } +}; + +#define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) +#define STR(Str) TYPED_LITERAL(charT, Str) + +struct basic_custom_formattable_type { string_view string_content; }; template <> -struct std::formatter { +struct std::formatter { basic_format_parse_context::iterator parse(basic_format_parse_context& parse_ctx) { if (parse_ctx.begin() != parse_ctx.end()) { throw format_error{"only empty specs please"}; } return parse_ctx.end(); } - format_context::iterator format(const custom_formattable_type& val, format_context& ctx) { + format_context::iterator format(const basic_custom_formattable_type& val, format_context& ctx) { ctx.advance_to(copy(val.string_content.begin(), val.string_content.end(), ctx.out())); return ctx.out(); } }; +template +struct custom_formattable_type { + T val; +}; + +template +struct std::formatter, charT> : std::formatter { + template + auto format(const custom_formattable_type& val, FC& format_ctx) { + return std::formatter::format(val.val, format_ctx); + } +}; + constexpr void test_disabled_formatter_is_disabled() { using F = formatter; + static_assert(!is_default_constructible_v); static_assert(!is_copy_constructible_v); static_assert(!is_move_constructible_v); @@ -36,7 +71,78 @@ constexpr void test_disabled_formatter_is_disabled() { static_assert(!is_move_assignable_v); } +template +void test_custom_equiv_with_format(const charT* fmt_string, const T& val) { + assert(format(fmt_string, custom_formattable_type{val}) == format(fmt_string, val)); +} + +template +void test_custom_equiv_with_format_mixed(const charT* fmt_string, const T& val) { + assert(format(fmt_string, custom_formattable_type{val}, val) == format(fmt_string, val, val)); +} + +template +void test_numeric_custom_formattable_type() { + test_custom_equiv_with_format(STR("{}"), 0); + test_custom_equiv_with_format(STR("{}"), 42); + test_custom_equiv_with_format(STR("{:+}"), 0); + test_custom_equiv_with_format(STR("{}"), numeric_limits::min()); + test_custom_equiv_with_format(STR("{}"), numeric_limits::max()); + test_custom_equiv_with_format(STR("{:3}"), 1); + if constexpr (!is_floating_point_v && !_Is_any_of_v) { + test_custom_equiv_with_format(STR("{:#x}"), 255); + test_custom_equiv_with_format(STR("{:#X}"), 255); + } +} + +template +void test_numeric_mixed_args_custom_formattable_type() { + test_custom_equiv_with_format_mixed(STR("{}{}"), 0); + test_custom_equiv_with_format_mixed(STR("{1}{0}"), 42); + test_custom_equiv_with_format_mixed(STR("{:+}{:-}"), 0); + test_custom_equiv_with_format_mixed(STR("{}{}"), numeric_limits::min()); + test_custom_equiv_with_format_mixed(STR("{}{}"), numeric_limits::max()); + test_custom_equiv_with_format_mixed(STR("{:3}{:4}"), 1); + test_custom_equiv_with_format_mixed(STR("{0}{0}"), 42); + if constexpr (!is_floating_point_v && !_Is_any_of_v) { + test_custom_equiv_with_format_mixed(STR("{:#x}{}"), 255); + test_custom_equiv_with_format_mixed(STR("{:#X}{}"), 255); + } +} + +template +void test_custom_formattable_type() { + test_numeric_custom_formattable_type(); + test_numeric_custom_formattable_type(); + test_numeric_custom_formattable_type(); + test_numeric_custom_formattable_type(); + test_numeric_custom_formattable_type(); +#ifdef _NATIVE_WCHAR_T_DEFINED + test_numeric_custom_formattable_type(); +#endif + test_numeric_custom_formattable_type(); + test_numeric_custom_formattable_type(); +} + +template +void test_mixed_custom_formattable_type() { + test_numeric_mixed_args_custom_formattable_type(); + test_numeric_mixed_args_custom_formattable_type(); + test_numeric_mixed_args_custom_formattable_type(); + test_numeric_mixed_args_custom_formattable_type(); + test_numeric_mixed_args_custom_formattable_type(); +#ifdef _NATIVE_WCHAR_T_DEFINED + test_numeric_mixed_args_custom_formattable_type(); +#endif + test_numeric_mixed_args_custom_formattable_type(); + test_numeric_mixed_args_custom_formattable_type(); +} + int main() { - assert(format("{}", custom_formattable_type{"f"}) == "f"s); + assert(format("{}", basic_custom_formattable_type{"f"}) == "f"s); + test_custom_formattable_type(); + test_custom_formattable_type(); + test_mixed_custom_formattable_type(); + test_mixed_custom_formattable_type(); return 0; } From fc1b784ec863cfa91428384abcbc21bce27496ea Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Thu, 1 Apr 2021 13:11:52 -0700 Subject: [PATCH 33/94] : Add _NODISCARD (#1796) * : Add _NODISCARD Adds `_NODISCARD` to many functions and `const` to pubilc facing ones. * Ignore result and remove nodiscard void --- stl/inc/format | 150 ++++++++++-------- .../test.cpp | 4 +- .../test.cpp | 2 +- 3 files changed, 85 insertions(+), 71 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 390d6af5116..57a46428c20 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -58,10 +58,10 @@ enum class _Basic_format_arg_type : uint8_t { }; static_assert(static_cast(_Basic_format_arg_type::_Custom_type) <= 16); -constexpr bool _Is_integral_fmt_type(_Basic_format_arg_type _Ty) { +_NODISCARD constexpr bool _Is_integral_fmt_type(_Basic_format_arg_type _Ty) { return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_ULong_long_type; } -constexpr bool _Is_arithmetic_fmt_type(_Basic_format_arg_type _Ty) { +_NODISCARD constexpr bool _Is_arithmetic_fmt_type(_Basic_format_arg_type _Ty) { return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_Long_double_type; } struct _Auto_id_tag {}; @@ -146,7 +146,7 @@ public: // _Next_arg_id > 0 means automatic // _Next_arg_id == 0 means unknown // _Next_arg_id < 0 means manual - constexpr size_t next_arg_id() { + _NODISCARD constexpr size_t next_arg_id() { if (_Next_arg_id < 0) { throw format_error("Can not switch from manual to automatic indexing"); } @@ -293,7 +293,8 @@ auto visit_format_arg(_Visitor&& _Vis, basic_format_arg<_Context> _Arg) { // we need to implement this ourselves because from_chars does not work with wide characters and isn't constexpr template -constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _CharT* _End, unsigned int& _Value) { +_NODISCARD constexpr const _CharT* _Parse_nonnegative_integer( + const _CharT* _Begin, const _CharT* _End, unsigned int& _Value) { _STL_INTERNAL_CHECK(_Begin != _End && '0' <= *_Begin && *_Begin <= '9'); _Value = 0; constexpr unsigned int _Max_int = static_cast((numeric_limits::max)()); @@ -314,7 +315,7 @@ constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _ } template -constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _CharT* _End, int& _Value) { +_NODISCARD constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _CharT* _End, int& _Value) { unsigned int _Val_unsigned = 0; _Begin = _Parse_nonnegative_integer(_Begin, _End, _Val_unsigned); @@ -324,7 +325,8 @@ constexpr const _CharT* _Parse_nonnegative_integer(const _CharT* _Begin, const _ } template _Callbacks_type> -constexpr const _CharT* _Parse_arg_id(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { +_NODISCARD constexpr const _CharT* _Parse_arg_id( + const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { _STL_INTERNAL_CHECK(_Begin != _End); _CharT _Ch = *_Begin; // No id provided, format string is using automatic indexing. @@ -358,7 +360,8 @@ constexpr const _CharT* _Parse_arg_id(const _CharT* _Begin, const _CharT* _End, } template _Callbacks_type> -constexpr const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { +_NODISCARD constexpr 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; @@ -452,7 +455,8 @@ struct _Id_adapter { }; template _Callbacks_type> -constexpr const _CharT* _Parse_width(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { +_NODISCARD constexpr const _CharT* _Parse_width( + const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { _STL_INTERNAL_CHECK(_Begin != _End); if ('1' <= *_Begin && *_Begin <= '9') { int _Value = 0; @@ -472,7 +476,8 @@ constexpr const _CharT* _Parse_width(const _CharT* _Begin, const _CharT* _End, _ } template _Callbacks_type> -constexpr const _CharT* _Parse_precision(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { +_NODISCARD constexpr const _CharT* _Parse_precision( + const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { ++_Begin; _CharT _Ch = '\0'; if (_Begin != _End) { @@ -499,7 +504,8 @@ constexpr const _CharT* _Parse_precision(const _CharT* _Begin, const _CharT* _En } template _Callbacks_type> -constexpr const _CharT* _Parse_format_specs(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { +_NODISCARD constexpr const _CharT* _Parse_format_specs( + const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { if (_Begin == _End || *_Begin == '}') { return _Begin; } @@ -571,7 +577,8 @@ constexpr const _CharT* _Parse_format_specs(const _CharT* _Begin, const _CharT* } template _HandlerT> -constexpr const _CharT* _Parse_replacement_field(const _CharT* _Begin, const _CharT* _End, _HandlerT&& _Handler) { +_NODISCARD constexpr const _CharT* _Parse_replacement_field( + const _CharT* _Begin, const _CharT* _End, _HandlerT&& _Handler) { ++_Begin; if (_Begin == _End) { throw format_error("Invalid format string."); @@ -725,7 +732,7 @@ protected: }; template -constexpr basic_format_arg<_Context> _Get_arg(_Context _Ctx, size_t _Arg_id) { +_NODISCARD constexpr basic_format_arg<_Context> _Get_arg(_Context _Ctx, size_t _Arg_id) { // note: while this is parameterized on the _Arg_id type in libfmt we don't // need to do that in std::format because it's only called with either an integer // id or a named id (which we do not support in std::format) @@ -741,7 +748,7 @@ constexpr basic_format_arg<_Context> _Get_arg(_Context _Ctx, size_t _Arg_id) { class _Width_checker { public: template - constexpr unsigned long long operator()(_Ty _Value) { + _NODISCARD constexpr unsigned long long operator()(_Ty _Value) { if constexpr (is_integral_v<_Ty>) { if constexpr (is_signed_v<_Ty>) { if (_Value < 0) { @@ -760,7 +767,7 @@ public: class _Precision_checker { public: template - constexpr unsigned long long operator()(_Ty _Value) { + _NODISCARD constexpr unsigned long long operator()(_Ty _Value) { if constexpr (is_integral_v<_Ty>) { if constexpr (is_signed_v<_Ty>) { if (_Value < 0) { @@ -813,11 +820,11 @@ private: _ParseContext& _Parse_ctx; _Context& _Ctx; - constexpr basic_format_arg<_Context> _Get_arg(_Auto_id_tag) { + _NODISCARD constexpr basic_format_arg<_Context> _Get_arg(_Auto_id_tag) { return _STD _Get_arg(_Ctx, _Parse_ctx.next_arg_id()); } - constexpr basic_format_arg<_Context> _Get_arg(const size_t _Arg_id) { + _NODISCARD constexpr basic_format_arg<_Context> _Get_arg(const size_t _Arg_id) { _Parse_ctx.check_arg_id(_Arg_id); return _STD _Get_arg(_Ctx, _Arg_id); } @@ -933,7 +940,7 @@ concept _Has_formatter = requires(const _Ty& _Val, _Context& _Ctx) { // clang-format on template -constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type); +_NODISCARD constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type); // See N4878 [format.arg]/5 // clang-format off @@ -1134,7 +1141,7 @@ public: basic_format_args(const _Format_arg_store<_Context, _Args...>& _Store) noexcept : _Num_args(sizeof...(_Args)), _Storage(_Store._Storage) {} - basic_format_arg<_Context> get(const size_t _Index) const noexcept { + _NODISCARD basic_format_arg<_Context> get(const size_t _Index) const noexcept { if (_Index >= _Num_args) { return basic_format_arg<_Context>{}; } @@ -1207,14 +1214,14 @@ public: _Out _OutputIt_, basic_format_args _Ctx_args, const locale& _Loc_) : _OutputIt(_OutputIt_), _Args(_Ctx_args), _Loc(_Loc_) {} - basic_format_arg arg(size_t _Id) const { + _NODISCARD basic_format_arg arg(size_t _Id) const { return _Args.get(_Id); } - locale locale() { + _NODISCARD locale locale() { return _Loc; } - iterator out() { + _NODISCARD iterator out() { return _OutputIt; } void advance_to(iterator _It) { @@ -1222,13 +1229,13 @@ public: _OutputIt = _It; } - const basic_format_args& _Get_args() const { + _NODISCARD const basic_format_args& _Get_args() const { return _Args; } }; template -constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type) { +_NODISCARD constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type) { switch (_Type) { case _Basic_format_arg_type::_Int_type: return sizeof(int); @@ -1264,7 +1271,7 @@ constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type) } template -_OutputIt _Write(_OutputIt _Out, monostate) { +_NODISCARD _OutputIt _Write(_OutputIt _Out, monostate) { _STL_INTERNAL_CHECK(false); return _Out; } @@ -1280,7 +1287,7 @@ inline constexpr size_t _Format_min_buffer_length = 24; // clang-format off template requires (is_arithmetic_v<_Arithmetic> && !_CharT_or_bool<_Arithmetic, _CharT>) -_OutputIt _Write(_OutputIt _Out, const _Arithmetic _Value) { +_NODISCARD _OutputIt _Write(_OutputIt _Out, const _Arithmetic _Value) { // clang-format on // TRANSITION, Reusable buffer array _Buffer; @@ -1291,7 +1298,7 @@ _OutputIt _Write(_OutputIt _Out, const _Arithmetic _Value) { #pragma warning(pop) template -_OutputIt _Write(_OutputIt _Out, const bool _Value) { +_NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value) { if constexpr (is_same_v<_CharT, wchar_t>) { return _Write(_Out, _Value ? L"true" : L"false"); } else { @@ -1300,7 +1307,7 @@ _OutputIt _Write(_OutputIt _Out, const bool _Value) { } template -_OutputIt _Write(_OutputIt _Out, const _CharT _Value) { +_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value) { *_Out = _Value; return ++_Out; } @@ -1308,7 +1315,7 @@ _OutputIt _Write(_OutputIt _Out, const _CharT _Value) { #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template -_OutputIt _Write(_OutputIt _Out, const void* const _Value) { +_NODISCARD _OutputIt _Write(_OutputIt _Out, const void* const _Value) { // TRANSITION, Reusable buffer array _Buffer; const auto [_End, _Ec] = @@ -1321,7 +1328,7 @@ _OutputIt _Write(_OutputIt _Out, const void* const _Value) { #pragma warning(pop) template -_OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { +_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { if (!_Value) { throw format_error("String pointer is null."); } @@ -1332,12 +1339,12 @@ _OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { } template -_OutputIt _Write(_OutputIt _Out, const basic_string_view<_CharT> _Value) { +_NODISCARD _OutputIt _Write(_OutputIt _Out, const basic_string_view<_CharT> _Value) { return _STD copy(_Value.begin(), _Value.end(), _Out); } template -_OutputIt _Write_aligned(_OutputIt _Out, const int _Width, const _Basic_format_specs<_CharT>& _Specs, +_NODISCARD _OutputIt _Write_aligned(_OutputIt _Out, const int _Width, const _Basic_format_specs<_CharT>& _Specs, const _Align _Default_align, _Func&& _Fn) { int _Fill_left = 0; int _Fill_right = 0; @@ -1393,7 +1400,7 @@ _NODISCARD constexpr string_view _Get_integral_prefix(const char _Type, const _I } template -_OutputIt _Write_sign(_OutputIt _Out, const _Sign _Sgn, const bool _Is_negative) { +_NODISCARD _OutputIt _Write_sign(_OutputIt _Out, const _Sign _Sgn, const bool _Is_negative) { if (_Is_negative) { *_Out++ = '-'; } else { @@ -1448,7 +1455,7 @@ _NODISCARD inline int _Count_separators(size_t _Digits, const string_view _Group } template -_OutputIt _Write_separated_integer(const char* _Start, const char* const _End, const string_view _Groups, +_NODISCARD _OutputIt _Write_separated_integer(const char* _Start, const char* const _End, const string_view _Groups, const _CharT _Separator, int _Separators, _OutputIt _Out) { auto _Group_it = _Groups.begin(); auto _Repeats = 0; @@ -1481,7 +1488,7 @@ _OutputIt _Write_separated_integer(const char* _Start, const char* const _End, c } template -_OutputIt _Write(_OutputIt _Out, monostate, const _Basic_format_specs<_CharT>&, locale) { +_NODISCARD _OutputIt _Write(_OutputIt _Out, monostate, const _Basic_format_specs<_CharT>&, locale) { _STL_INTERNAL_CHECK(false); return _Out; } @@ -1489,7 +1496,8 @@ _OutputIt _Write(_OutputIt _Out, monostate, const _Basic_format_specs<_CharT>&, #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template -_OutputIt _Write_integral(_OutputIt _Out, const _Integral _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { +_NODISCARD _OutputIt _Write_integral( + _OutputIt _Out, const _Integral _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { if (_Specs._Type == 'c') { if (!_In_bounds<_CharT>(_Value)) { throw format_error("integral cannot be stored in charT"); @@ -1593,13 +1601,14 @@ _OutputIt _Write_integral(_OutputIt _Out, const _Integral _Value, _Basic_format_ // clang-format off template requires (!_CharT_or_bool<_Integral, _CharT>) -_OutputIt _Write(_OutputIt _Out, const _Integral _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { +_NODISCARD _OutputIt _Write( + _OutputIt _Out, const _Integral _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { // clang-format on return _Write_integral(_Out, _Value, _Specs, _Locale); } template -_OutputIt _Write(_OutputIt _Out, const bool _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { +_NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { if (_Specs._Type != '\0' && _Specs._Type != 's') { return _Write_integral(_Out, static_cast(_Value), _Specs, _Locale); } @@ -1624,7 +1633,7 @@ _OutputIt _Write(_OutputIt _Out, const bool _Value, _Basic_format_specs<_CharT> } template -_OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { +_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { if (_Specs._Type != '\0' && _Specs._Type != 'c') { return _Write_integral(_Out, _Value, _Specs, _Locale); } @@ -1641,7 +1650,8 @@ _OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template -_OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { +_NODISCARD _OutputIt _Write( + _OutputIt _Out, const _Float _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { auto _Sgn = _Specs._Sgn; if (_Sgn == _Sign::_None) { _Sgn = _Sign::_Minus; @@ -1837,7 +1847,8 @@ _OutputIt _Write(_OutputIt _Out, const _Float _Value, const _Basic_format_specs< #pragma warning(pop) template -_OutputIt _Write(_OutputIt _Out, const void* const _Value, const _Basic_format_specs<_CharT>& _Specs, locale) { +_NODISCARD _OutputIt _Write( + _OutputIt _Out, const void* const _Value, const _Basic_format_specs<_CharT>& _Specs, locale) { if (_Specs._Type != '\0' && _Specs._Type != 'p') { throw format_error("invalid const void* type"); } @@ -1878,12 +1889,13 @@ _OutputIt _Write(_OutputIt _Out, const void* const _Value, const _Basic_format_s } template -_OutputIt _Write(_OutputIt _Out, const _CharT* _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { +_NODISCARD _OutputIt _Write( + _OutputIt _Out, const _CharT* _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { return _Write(_Out, basic_string_view<_CharT>{_Value}, _Specs, _Locale); } template -_OutputIt _Write( +_NODISCARD _OutputIt _Write( _OutputIt _Out, const basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs, locale) { if (_Specs._Type != '\0' && _Specs._Type != 's') { throw format_error("invalid string type"); @@ -2104,12 +2116,12 @@ using format_args = basic_format_args; using wformat_args = basic_format_args; template -auto make_format_args(const _Args&... _Vals) { +_NODISCARD auto make_format_args(const _Args&... _Vals) { return _Format_arg_store<_Context, _Args...>{_Vals...}; } template -auto make_wformat_args(const _Args&... _Vals) { +_NODISCARD auto make_wformat_args(const _Args&... _Vals) { return _Format_arg_store<_Context, _Args...>{_Vals...}; } @@ -2117,7 +2129,8 @@ template using format_args_t = basic_format_args>; template _OutputIt> -_OutputIt vformat_to(_OutputIt _Out, string_view _Fmt, format_args_t, char> _Args) { +_OutputIt vformat_to( + _OutputIt _Out, const string_view _Fmt, const format_args_t, char> _Args) { _Format_handler<_OutputIt, char, basic_format_context<_OutputIt, char>> _Handler( _Out, _Fmt, _Args, locale::classic()); _Parse_format_string(_Fmt, _Handler); @@ -2125,7 +2138,8 @@ _OutputIt vformat_to(_OutputIt _Out, string_view _Fmt, format_args_t _OutputIt> -_OutputIt vformat_to(_OutputIt _Out, wstring_view _Fmt, format_args_t, wchar_t> _Args) { +_OutputIt vformat_to( + _OutputIt _Out, const wstring_view _Fmt, const format_args_t, wchar_t> _Args) { _Format_handler<_OutputIt, wchar_t, basic_format_context<_OutputIt, wchar_t>> _Handler( _Out, _Fmt, _Args, locale::classic()); _Parse_format_string(_Fmt, _Handler); @@ -2133,86 +2147,86 @@ _OutputIt vformat_to(_OutputIt _Out, wstring_view _Fmt, format_args_t _OutputIt> -_OutputIt vformat_to( - _OutputIt _Out, const locale& _Loc, string_view _Fmt, format_args_t, char> _Args) { +_OutputIt vformat_to(_OutputIt _Out, const locale& _Loc, const string_view _Fmt, + const format_args_t, char> _Args) { _Format_handler<_OutputIt, char, basic_format_context<_OutputIt, char>> _Handler(_Out, _Fmt, _Args, _Loc); _Parse_format_string(_Fmt, _Handler); return _Handler._Ctx.out(); } template _OutputIt> -_OutputIt vformat_to( - _OutputIt _Out, const locale& _Loc, wstring_view _Fmt, format_args_t, wchar_t> _Args) { +_OutputIt vformat_to(_OutputIt _Out, const locale& _Loc, const wstring_view _Fmt, + const format_args_t, wchar_t> _Args) { _Format_handler<_OutputIt, wchar_t, basic_format_context<_OutputIt, wchar_t>> _Handler(_Out, _Fmt, _Args, _Loc); _Parse_format_string(_Fmt, _Handler); return _Handler._Ctx.out(); } template -_OutputIt format_to(_OutputIt _Out, string_view _Fmt, const _Types&... _Args) { +_OutputIt format_to(_OutputIt _Out, const string_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_OutputIt, char>; return _STD vformat_to(_STD move(_Out), _Fmt, _STD make_format_args<_Context>(_Args...)); } template -_OutputIt format_to(_OutputIt _Out, wstring_view _Fmt, const _Types&... _Args) { +_OutputIt format_to(_OutputIt _Out, const wstring_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_OutputIt, wchar_t>; return _STD vformat_to(_STD move(_Out), _Fmt, _STD make_format_args<_Context>(_Args...)); } template -_OutputIt format_to(_OutputIt _Out, const locale& _Loc, string_view _Fmt, const _Types&... _Args) { +_OutputIt format_to(_OutputIt _Out, const locale& _Loc, const string_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_OutputIt, char>; return _STD vformat_to(_STD move(_Out), _Loc, _Fmt, _STD make_format_args<_Context>(_Args...)); } template -_OutputIt format_to(_OutputIt _Out, const locale& _Loc, wstring_view _Fmt, const _Types&... _Args) { +_OutputIt format_to(_OutputIt _Out, const locale& _Loc, const wstring_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_OutputIt, wchar_t>; return _STD vformat_to(_STD move(_Out), _Loc, _Fmt, _STD make_format_args<_Context>(_Args...)); } -inline string vformat(string_view _Fmt, format_args _Args) { +_NODISCARD inline string vformat(const string_view _Fmt, const format_args _Args) { string _Str; _STD vformat_to(_STD back_inserter(_Str), _Fmt, _Args); return _Str; } -inline wstring vformat(wstring_view _Fmt, wformat_args _Args) { +_NODISCARD inline wstring vformat(const wstring_view _Fmt, const wformat_args _Args) { wstring _Str; _STD vformat_to(_STD back_inserter(_Str), _Fmt, _Args); return _Str; } -inline string vformat(const locale& _Loc, string_view _Fmt, format_args _Args) { +_NODISCARD inline string vformat(const locale& _Loc, const string_view _Fmt, const format_args _Args) { string _Str; _STD vformat_to(_STD back_inserter(_Str), _Loc, _Fmt, _Args); return _Str; } -inline wstring vformat(const locale& _Loc, wstring_view _Fmt, wformat_args _Args) { +_NODISCARD inline wstring vformat(const locale& _Loc, const wstring_view _Fmt, const wformat_args _Args) { wstring _Str; _STD vformat_to(_STD back_inserter(_Str), _Loc, _Fmt, _Args); return _Str; } template -string format(string_view _Fmt, const _Types&... _Args) { +_NODISCARD string format(const string_view _Fmt, const _Types&... _Args) { return _STD vformat(_Fmt, _STD make_format_args(_Args...)); } template -wstring format(wstring_view _Fmt, const _Types&... _Args) { +_NODISCARD wstring format(const wstring_view _Fmt, const _Types&... _Args) { return _STD vformat(_Fmt, _STD make_wformat_args(_Args...)); } template -string format(const locale& _Loc, string_view _Fmt, const _Types&... _Args) { +_NODISCARD string format(const locale& _Loc, const string_view _Fmt, const _Types&... _Args) { return _STD vformat(_Loc, _Fmt, _STD make_format_args(_Args...)); } template -wstring format(const locale& _Loc, wstring_view _Fmt, const _Types&... _Args) { +_NODISCARD wstring format(const locale& _Loc, const wstring_view _Fmt, const _Types&... _Args) { return _STD vformat(_Loc, _Fmt, _STD make_wformat_args(_Args...)); } @@ -2232,7 +2246,7 @@ struct _Format_to_n_iterator { return *this; } - _Format_to_n_iterator& operator*() { + _NODISCARD _Format_to_n_iterator& operator*() { return *this; } @@ -2306,7 +2320,7 @@ struct _Counting_iterator { return *this; } - _Counting_iterator& operator*() { + _NODISCARD _Counting_iterator& operator*() { return *this; } @@ -2320,28 +2334,28 @@ struct _Counting_iterator { }; template -size_t formatted_size(const string_view _Fmt, const _Types&... _Args) { +_NODISCARD size_t formatted_size(const string_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_Counting_iterator, char>; auto _Arg_store = _STD make_format_args<_Context>(_Args...); return _STD vformat_to(_Counting_iterator{}, _Fmt, _STD move(_Arg_store))._Count; } template -size_t formatted_size(const wstring_view _Fmt, const _Types&... _Args) { +_NODISCARD size_t formatted_size(const wstring_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_Counting_iterator, wchar_t>; auto _Arg_store = _STD make_format_args<_Context>(_Args...); return _STD vformat_to(_Counting_iterator{}, _Fmt, _STD move(_Arg_store))._Count; } template -size_t formatted_size(const locale& _Loc, const string_view _Fmt, const _Types&... _Args) { +_NODISCARD size_t formatted_size(const locale& _Loc, const string_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_Counting_iterator, char>; auto _Arg_store = _STD make_format_args<_Context>(_Args...); return _STD vformat_to(_Counting_iterator{}, _Loc, _Fmt, _STD move(_Arg_store))._Count; } template -size_t formatted_size(const locale& _Loc, const wstring_view _Fmt, const _Types&... _Args) { +_NODISCARD size_t formatted_size(const locale& _Loc, const wstring_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_Counting_iterator, wchar_t>; auto _Arg_store = _STD make_format_args<_Context>(_Args...); return _STD vformat_to(_Counting_iterator{}, _Loc, _Fmt, _STD move(_Arg_store))._Count; diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 766f4f47c7b..1e7c413f80d 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -54,7 +54,7 @@ auto make_testing_format_args(Args&&... vals) { template void throw_helper(const charT* fmt, const Args&... vals) { try { - format(fmt, vals...); + (void) format(fmt, vals...); assert(false); } catch (const format_error&) { } @@ -385,7 +385,7 @@ void test_fill_and_align() { auto tester = [&] { basic_string output_string; - _Write_aligned(back_inserter(output_string), 2, specs, _Align::_Left, writer); + (void) _Write_aligned(back_inserter(output_string), 2, specs, _Align::_Left, writer); return output_string; }; diff --git a/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp b/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp index 606f08b51f4..1f62d3529d3 100644 --- a/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parse_contexts/test.cpp @@ -86,7 +86,7 @@ constexpr bool test_basic_format_parse_context() { if (!is_constant_evaluated()) { try { - context.next_arg_id(); + (void) context.next_arg_id(); } catch (const format_error& e) { assert(e.what() == "Can not switch from manual to automatic indexing"sv); } From 30e99e4bb38f88c8c3dcfd7bd4a0ee6ca7488658 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Tue, 6 Apr 2021 18:05:26 -0700 Subject: [PATCH 34/94] : Fix arg overload resolution (#1804) * : Fix arg overload resolution There seems to be a compiler bug picking the wrong function in an overload set. I've filed it here: . This sidesteps the bug, and also fixes a latent bug with `_Get_format_arg_storage_typee`. There is also a fix for the packed index, that wasn't very packed because the types of the bitset were different. This should save space as intended. * Comments --- stl/inc/format | 41 ++++++++++++------- .../P0645R10_text_formatting_args/test.cpp | 3 ++ 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 57a46428c20..24d4ca5c533 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -976,19 +976,21 @@ template } } -template -/* consteval */ constexpr auto _Get_format_arg_storage_type(const _Char_type*) noexcept { - return static_cast(nullptr); +template +/* consteval */ constexpr auto _Get_format_arg_storage_type(const typename _Context::char_type*) noexcept { + return static_cast(nullptr); } -template -/* consteval */ constexpr auto _Get_format_arg_storage_type(basic_string_view<_Char_type, _Traits>) noexcept { - return basic_string_view<_Char_type>{}; +template +/* consteval */ constexpr auto _Get_format_arg_storage_type( + basic_string_view) noexcept { + return basic_string_view{}; } -template -/* consteval */ constexpr auto _Get_format_arg_storage_type(const basic_string<_Char_type, _Traits, _Alloc>&) noexcept { - return basic_string_view<_Char_type>{}; +template +/* consteval */ constexpr auto _Get_format_arg_storage_type( + const basic_string&) noexcept { + return basic_string_view{}; } template @@ -1006,18 +1008,27 @@ template template inline constexpr size_t _Get_format_arg_storage_size = sizeof( - _Get_format_arg_storage_type<_Context>(_STD declval<_Ty>())); + _Get_format_arg_storage_type<_Context>(_STD declval())); struct _Format_arg_store_packed_index { // TRANSITION, Should be templated on number of arguments for even less storage using _Index_type = size_t; constexpr _Format_arg_store_packed_index() = default; - constexpr explicit _Format_arg_store_packed_index(const size_t _Index) - : _Index(static_cast<_Index_type>(_Index)), _Type(_Basic_format_arg_type::_None) {} + constexpr explicit _Format_arg_store_packed_index(const size_t _Index) : _Index(static_cast<_Index_type>(_Index)) { + _Type(_Basic_format_arg_type::_None); + } + + _NODISCARD constexpr _Basic_format_arg_type _Type() const noexcept { + return static_cast<_Basic_format_arg_type>(_Type_); + } + + constexpr void _Type(_Basic_format_arg_type _Val) noexcept { + _Type_ = static_cast<_Index_type>(_Val); + } _Index_type _Index : (sizeof(_Index_type) * 8 - 4); - _Basic_format_arg_type _Type : 4; + _Index_type _Type_ : 4; }; template @@ -1045,7 +1056,7 @@ private: const auto _Length = _Get_format_arg_type_storage_size<_CharType>(_Arg_type); _CSTD memcpy(_Storage + _Index_length + _Store_index, _STD addressof(_Val), _Length); - _Index_array[_Arg_index]._Type = _Arg_type; + _Index_array[_Arg_index]._Type(_Arg_type); if (_Arg_index + 1 < _Num_args) { // Set the starting index of the next arg, as that is dynamic, must be called with increasing index _Index_array[_Arg_index + 1] = _Format_arg_store_packed_index{_Store_index + _Length}; @@ -1151,7 +1162,7 @@ public: const auto _Index_length = _Num_args * sizeof(_Format_arg_store_packed_index); const unsigned char* _Arg_storage = _Storage + _Index_length + _Packed_index._Index; - switch (_Packed_index._Type) { + switch (_Packed_index._Type()) { case _Basic_format_arg_type::_None: return basic_format_arg<_Context>{}; case _Basic_format_arg_type::_Int_type: diff --git a/tests/std/tests/P0645R10_text_formatting_args/test.cpp b/tests/std/tests/P0645R10_text_formatting_args/test.cpp index e95ba049f9c..7264f450826 100644 --- a/tests/std/tests/P0645R10_text_formatting_args/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_args/test.cpp @@ -213,6 +213,9 @@ void test_format_arg_store() { test_single_format_arg, Arg_type::string_type>(get_input_sv()); } +static_assert(sizeof(_Format_arg_store_packed_index) == sizeof(_Format_arg_store_packed_index::_Index_type)); +static_assert(_Get_format_arg_storage_size == sizeof(basic_format_arg::handle)); + int main() { test_basic_format_arg(); test_basic_format_arg(); From 334308a675862a62bc4fd87a78b82155aee5c644 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Tue, 6 Apr 2021 23:48:14 -0700 Subject: [PATCH 35/94] : Allow move-only output iterators (#1795) * : Allow move-only output iterators Now we move around the iterator we get instead of copying it. I had to move a few functions from their home in algorithm and xmemory into xutility. I hope I got the comments/guards correct, but please double check those. The testing is done by making sure we don't use-after-move a custom iterator and that all the printing functions are instantiated. --- stl/inc/algorithm | 60 ----- stl/inc/format | 208 +++++++++++------- stl/inc/xmemory | 21 -- stl/inc/xutility | 93 +++++++- .../test.cpp | 57 +++++ 5 files changed, 277 insertions(+), 162 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index e128b93569c..3e357089464 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1380,31 +1380,7 @@ namespace ranges { inline constexpr _None_of_fn none_of{_Not_quite_object::_Construct_tag{}}; - // ALIAS TEMPLATE copy_result - 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) { - 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)); - return {_STD move(_Final), _STD move(_Result)}; - } - } - - for (; _First != _Last; ++_First, (void) ++_Result) { - *_Result = *_First; - } - - 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; @@ -3534,42 +3510,6 @@ namespace ranges { inline constexpr _Fill_fn fill{_Not_quite_object::_Construct_tag{}}; - // VARIABLE ranges::fill_n - class _Fill_n_fn : private _Not_quite_object { - public: - using _Not_quite_object::_Not_quite_object; - - template _It> - constexpr _It operator()(_It _First, iter_difference_t<_It> _Count, const _Ty& _Value) const { - if (_Count > 0) { - auto _UFirst = _Get_unwrapped_n(_STD move(_First), _Count); - if (!_STD is_constant_evaluated()) { - if constexpr (_Fill_memset_is_safe) { - _Fill_memset(_UFirst, _Value, static_cast(_Count)); - _Seek_wrapped(_First, _UFirst + _Count); // no need to move since _UFirst is a pointer - return _First; - } else if constexpr (_Fill_zero_memset_is_safe) { - if (_Is_all_bits_zero(_Value)) { - _Fill_zero_memset(_UFirst, static_cast(_Count)); - _Seek_wrapped(_First, _UFirst + _Count); // no need to move since _UFirst is a pointer - return _First; - } - } - } - - for (; _Count > 0; ++_UFirst, (void) --_Count) { - *_UFirst = _Value; - } - - _Seek_wrapped(_First, _STD move(_UFirst)); - } - - return _First; - } - }; - - inline constexpr _Fill_n_fn fill_n{_Not_quite_object::_Construct_tag{}}; - // VARIABLE ranges::generate class _Generate_fn : private _Not_quite_object { public: diff --git a/stl/inc/format b/stl/inc/format index 24d4ca5c533..5feac98cda4 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -258,36 +258,36 @@ template auto visit_format_arg(_Visitor&& _Vis, basic_format_arg<_Context> _Arg) { switch (_Arg._Active_state) { case _Basic_format_arg_type::_None: - return _Vis(_Arg._No_state); + return _STD forward<_Visitor>(_Vis)(_Arg._No_state); case _Basic_format_arg_type::_Int_type: - return _Vis(_Arg._Int_state); + return _STD forward<_Visitor>(_Vis)(_Arg._Int_state); case _Basic_format_arg_type::_UInt_type: - return _Vis(_Arg._UInt_state); + return _STD forward<_Visitor>(_Vis)(_Arg._UInt_state); case _Basic_format_arg_type::_Long_long_type: - return _Vis(_Arg._Long_long_state); + return _STD forward<_Visitor>(_Vis)(_Arg._Long_long_state); case _Basic_format_arg_type::_ULong_long_type: - return _Vis(_Arg._ULong_long_state); + return _STD forward<_Visitor>(_Vis)(_Arg._ULong_long_state); case _Basic_format_arg_type::_Bool_type: - return _Vis(_Arg._Bool_state); + return _STD forward<_Visitor>(_Vis)(_Arg._Bool_state); case _Basic_format_arg_type::_Char_type: - return _Vis(_Arg._Char_state); + return _STD forward<_Visitor>(_Vis)(_Arg._Char_state); case _Basic_format_arg_type::_Float_type: - return _Vis(_Arg._Float_state); + return _STD forward<_Visitor>(_Vis)(_Arg._Float_state); case _Basic_format_arg_type::_Double_type: - return _Vis(_Arg._Double_state); + return _STD forward<_Visitor>(_Vis)(_Arg._Double_state); case _Basic_format_arg_type::_Long_double_type: - return _Vis(_Arg._Long_double_state); + return _STD forward<_Visitor>(_Vis)(_Arg._Long_double_state); case _Basic_format_arg_type::_Pointer_type: - return _Vis(_Arg._Pointer_state); + return _STD forward<_Visitor>(_Vis)(_Arg._Pointer_state); case _Basic_format_arg_type::_CString_type: - return _Vis(_Arg._CString_state); + return _STD forward<_Visitor>(_Vis)(_Arg._CString_state); case _Basic_format_arg_type::_String_type: - return _Vis(_Arg._String_state); + return _STD forward<_Visitor>(_Vis)(_Arg._String_state); case _Basic_format_arg_type::_Custom_type: - return _Vis(_Arg._Custom_state); + return _STD forward<_Visitor>(_Vis)(_Arg._Custom_state); default: _STL_VERIFY(false, "basic_format_arg is in impossible state"); - return _Vis(0); + return _STD forward<_Visitor>(_Vis)(0); } } @@ -732,7 +732,7 @@ protected: }; template -_NODISCARD constexpr basic_format_arg<_Context> _Get_arg(_Context _Ctx, size_t _Arg_id) { +_NODISCARD constexpr basic_format_arg<_Context> _Get_arg(const _Context& _Ctx, size_t _Arg_id) { // note: while this is parameterized on the _Arg_id type in libfmt we don't // need to do that in std::format because it's only called with either an integer // id or a named id (which we do not support in std::format) @@ -1206,7 +1206,7 @@ private: // TODO: test coverage // clang-format off template - requires output_iterator<_Out, _CharT> + requires output_iterator<_Out, const _CharT&> class basic_format_context { // clang-format on private: @@ -1223,7 +1223,7 @@ public: constexpr basic_format_context( _Out _OutputIt_, basic_format_args _Ctx_args, const locale& _Loc_) - : _OutputIt(_OutputIt_), _Args(_Ctx_args), _Loc(_Loc_) {} + : _OutputIt(_STD move(_OutputIt_)), _Args(_Ctx_args), _Loc(_Loc_) {} _NODISCARD basic_format_arg arg(size_t _Id) const { return _Args.get(_Id); @@ -1233,11 +1233,11 @@ public: } _NODISCARD iterator out() { - return _OutputIt; + return _STD move(_OutputIt); } void advance_to(iterator _It) { // TODO: IDL support probably required - _OutputIt = _It; + _OutputIt = _STD move(_It); } _NODISCARD const basic_format_args& _Get_args() const { @@ -1293,6 +1293,27 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, monostate) { // = 17 + 1 + 1 + 1 + 3 = 24. An example is DBL_MAX which is "-1.7976931348623158e+308". inline constexpr size_t _Format_min_buffer_length = 24; +// clang-format off +template + requires(is_arithmetic_v<_Arithmetic> && !_CharT_or_bool<_Arithmetic, _CharT>) +_NODISCARD _OutputIt _Write(_OutputIt _Out, const _Arithmetic _Value); +// clang-format on + +template +_NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value); + +template +_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value); + +template +_NODISCARD _OutputIt _Write(_OutputIt _Out, const void* const _Value); + +template +_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT* _Value); + +template +_NODISCARD _OutputIt _Write(_OutputIt _Out, const basic_string_view<_CharT> _Value); + #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch // clang-format off @@ -1304,23 +1325,23 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, const _Arithmetic _Value) { array _Buffer; const auto [_End, _Ec] = _STD to_chars(_Buffer.data(), _Buffer.data() + _Buffer.size(), _Value); _STL_ASSERT(_Ec == errc{}, "to_chars failed"); - return _STD copy(_Buffer.data(), _End, _Out); + return _RANGES _Copy_unchecked(_Buffer.data(), _End, _STD move(_Out)).out; } #pragma warning(pop) template _NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value) { if constexpr (is_same_v<_CharT, wchar_t>) { - return _Write(_Out, _Value ? L"true" : L"false"); + return _Write(_STD move(_Out), _Value ? L"true" : L"false"); } else { - return _Write(_Out, _Value ? "true" : "false"); + return _Write(_STD move(_Out), _Value ? "true" : "false"); } } template _NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value) { - *_Out = _Value; - return ++_Out; + *_Out++ = _Value; + return _Out; } #pragma warning(push) @@ -1334,7 +1355,7 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, const void* const _Value) { _STL_ASSERT(_Ec == errc{}, "to_chars failed"); *_Out++ = '0'; *_Out++ = 'x'; - return _STD copy(_Buffer.data(), _End, _Out); + return _RANGES _Copy_unchecked(_Buffer.data(), _End, _STD move(_Out)).out; } #pragma warning(pop) @@ -1351,7 +1372,7 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { template _NODISCARD _OutputIt _Write(_OutputIt _Out, const basic_string_view<_CharT> _Value) { - return _STD copy(_Value.begin(), _Value.end(), _Out); + return _RANGES _Copy_unchecked(_Value.begin(), _Value.end(), _STD move(_Out)).out; } template @@ -1384,9 +1405,9 @@ _NODISCARD _OutputIt _Write_aligned(_OutputIt _Out, const int _Width, const _Bas } // TRANSITION, add support for unicode/wide formats - _Out = _STD fill_n(_Out, _Fill_left, _Specs._Fill[0]); - _Out = _Fn(_Out); - return _STD fill_n(_Out, _Fill_right, _Specs._Fill[0]); + _Out = _RANGES fill_n(_STD move(_Out), _Fill_left, _Specs._Fill[0]); + _Out = _Fn(_STD move(_Out)); + return _RANGES fill_n(_STD move(_Out), _Fill_right, _Specs._Fill[0]); } template @@ -1480,7 +1501,7 @@ _NODISCARD _OutputIt _Write_separated_integer(const char* _Start, const char* co ++_Repeats; } } - _Out = _STD copy(_Start, _End - _Grouped, _Out); + _Out = _RANGES _Copy_unchecked(_Start, _End - _Grouped, _STD move(_Out)).out; _Start = _End - _Grouped; for (; _Separators > 0; --_Separators) { @@ -1491,7 +1512,7 @@ _NODISCARD _OutputIt _Write_separated_integer(const char* _Start, const char* co } *_Out++ = _Separator; - _Out = _STD copy(_Start, _Start + *_Group_it, _Out); + _Out = _RANGES _Copy_unchecked(_Start, _Start + *_Group_it, _STD move(_Out)).out; _Start += *_Group_it; } _STL_INTERNAL_CHECK(_Start == _End); @@ -1504,6 +1525,39 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, monostate, const _Basic_format_specs return _Out; } +template +_NODISCARD _OutputIt _Write_integral( + _OutputIt _Out, const _Integral _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); + +// clang-format off +template + requires(!_CharT_or_bool<_Integral, _CharT>) +_NODISCARD _OutputIt _Write( + _OutputIt _Out, const _Integral _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale); +// clang-format on + +template +_NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); + +template +_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); + +template +_NODISCARD _OutputIt _Write( + _OutputIt _Out, const _Float _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale); + +template +_NODISCARD _OutputIt _Write( + _OutputIt _Out, const void* const _Value, const _Basic_format_specs<_CharT>& _Specs, locale); + +template +_NODISCARD _OutputIt _Write( + _OutputIt _Out, const _CharT* _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale); + +template +_NODISCARD _OutputIt _Write( + _OutputIt _Out, const basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs, locale); + #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template @@ -1514,7 +1568,7 @@ _NODISCARD _OutputIt _Write_integral( throw format_error("integral cannot be stored in charT"); } _Specs._Alt = false; - return _Write(_Out, static_cast<_CharT>(_Value), _Specs, _Locale); + return _Write(_STD move(_Out), static_cast<_CharT>(_Value), _Specs, _Locale); } if (_Specs._Precision != -1) { @@ -1589,23 +1643,23 @@ _NODISCARD _OutputIt _Write_integral( const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Align::_None; auto _Writer = [&, _End = _End](_OutputIt _Out) { - _Out = _Write_sign(_Out, _Specs._Sgn, _Value < _Integral{0}); - _Out = _STD copy(_Prefix.begin(), _Prefix.end(), _Out); + _Out = _Write_sign(_STD move(_Out), _Specs._Sgn, _Value < _Integral{0}); + _Out = _RANGES _Copy_unchecked(_Prefix.begin(), _Prefix.end(), _STD move(_Out)).out; if (_Write_leading_zeroes && _Width < _Specs._Width) { - _Out = _STD fill_n(_Out, _Specs._Width - _Width, '0'); + _Out = _RANGES fill_n(_STD move(_Out), _Specs._Width - _Width, '0'); } if (_Separators > 0) { return _Write_separated_integer(_Buffer_start, _End, _Groups, - _STD use_facet>(_Locale).thousands_sep(), _Separators, _Out); + _STD use_facet>(_Locale).thousands_sep(), _Separators, _STD move(_Out)); } - return _STD copy(_Buffer_start, _End, _Out); + return _RANGES _Copy_unchecked(_Buffer_start, _End, _STD move(_Out)).out; }; if (_Write_leading_zeroes) { - return _Writer(_Out); + return _Writer(_STD move(_Out)); } - return _Write_aligned(_Out, _Width, _Specs, _Align::_Right, _Writer); + return _Write_aligned(_STD move(_Out), _Width, _Specs, _Align::_Right, _Writer); } #pragma warning(pop) @@ -1615,13 +1669,13 @@ template _NODISCARD _OutputIt _Write( _OutputIt _Out, const _Integral _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { // clang-format on - return _Write_integral(_Out, _Value, _Specs, _Locale); + return _Write_integral(_STD move(_Out), _Value, _Specs, _Locale); } template _NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { if (_Specs._Type != '\0' && _Specs._Type != 's') { - return _Write_integral(_Out, static_cast(_Value), _Specs, _Locale); + return _Write_integral(_STD move(_Out), static_cast(_Value), _Specs, _Locale); } if (_Specs._Precision != -1) { @@ -1630,23 +1684,23 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value, _Basic_format_spe if (_Specs._Localized) { _Specs._Localized = false; - return _Write(_Out, + return _Write(_STD move(_Out), _Value ? static_cast>(_STD use_facet>(_Locale).truename()) : static_cast>(_STD use_facet>(_Locale).falsename()), _Specs, _Locale); } if constexpr (is_same_v<_CharT, wchar_t>) { - return _Write(_Out, _Value ? L"true" : L"false", _Specs, _Locale); + return _Write(_STD move(_Out), _Value ? L"true" : L"false", _Specs, _Locale); } else { - return _Write(_Out, _Value ? "true" : "false", _Specs, _Locale); + return _Write(_STD move(_Out), _Value ? "true" : "false", _Specs, _Locale); } } template _NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { if (_Specs._Type != '\0' && _Specs._Type != 'c') { - return _Write_integral(_Out, _Value, _Specs, _Locale); + return _Write_integral(_STD move(_Out), _Value, _Specs, _Locale); } if (_Specs._Precision != -1) { @@ -1655,7 +1709,7 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_s // Clear the type so that the string_view writer doesn't fail on 'c'. _Specs._Type = '\0'; - return _Write(_Out, basic_string_view<_CharT>{&_Value, 1}, _Specs, _Locale); + return _Write(_STD move(_Out), basic_string_view<_CharT>{&_Value, 1}, _Specs, _Locale); } #pragma warning(push) @@ -1816,15 +1870,15 @@ _NODISCARD _OutputIt _Write( const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Align::_None && _Is_finite; auto _Writer = [&](_OutputIt _Out) { - _Out = _Write_sign(_Out, _Sgn, _Is_negative); + _Out = _Write_sign(_STD move(_Out), _Sgn, _Is_negative); if (_Write_leading_zeroes && _Width < _Specs._Width) { - _Out = _STD fill_n(_Out, _Specs._Width - _Width, '0'); + _Out = _RANGES fill_n(_STD move(_Out), _Specs._Width - _Width, '0'); } if (_Specs._Localized) { _Out = _Write_separated_integer(_Buffer_start, _Integral_end, _Groups, - _STD use_facet>(_Locale).thousands_sep(), _Separators, _Out); + _STD use_facet>(_Locale).thousands_sep(), _Separators, _STD move(_Out)); if (_Radix_point != _Result.ptr || _Append_decimal) { *_Out++ = _STD use_facet>(_Locale).decimal_point(); _Append_decimal = false; @@ -1835,7 +1889,7 @@ _NODISCARD _OutputIt _Write( } } - _Out = _STD copy(_Buffer_start, _Exponent_start, _Out); + _Out = _RANGES _Copy_unchecked(_Buffer_start, _Exponent_start, _STD move(_Out)).out; _Buffer_start = _Exponent_start; if (_Specs._Alt && _Append_decimal) { @@ -1846,14 +1900,14 @@ _NODISCARD _OutputIt _Write( *_Out++ = '0'; } - return _STD copy(_Buffer_start, _Result.ptr, _Out); + return _RANGES _Copy_unchecked(_Buffer_start, _Result.ptr, _STD move(_Out)).out; }; if (_Write_leading_zeroes) { - return _Writer(_Out); + return _Writer(_STD move(_Out)); } - return _Write_aligned(_Out, _Width, _Specs, _Align::_Right, _Writer); + return _Write_aligned(_STD move(_Out), _Width, _Specs, _Align::_Right, _Writer); } #pragma warning(pop) @@ -1895,14 +1949,14 @@ _NODISCARD _OutputIt _Write( _Width = 3; } - return _Write_aligned( - _Out, _Width, _Specs, _Align::_Left, [=](_OutputIt _Out) { return _Write<_CharT>(_Out, _Value); }); + return _Write_aligned(_STD move(_Out), _Width, _Specs, _Align::_Left, + [=](_OutputIt _Out) { return _Write<_CharT>(_STD move(_Out), _Value); }); } template _NODISCARD _OutputIt _Write( _OutputIt _Out, const _CharT* _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { - return _Write(_Out, basic_string_view<_CharT>{_Value}, _Specs, _Locale); + return _Write(_STD move(_Out), basic_string_view<_CharT>{_Value}, _Specs, _Locale); } template @@ -1934,8 +1988,9 @@ _NODISCARD _OutputIt _Write( _Printed_size = _Specs._Precision; } - return _Write_aligned(_Out, _Printed_size, _Specs, _Align::_Left, - [=](_OutputIt _Out) { return _Write(_Out, _Value.substr(size_t{0}, static_cast(_Printed_size))); }); + return _Write_aligned(_STD move(_Out), _Printed_size, _Specs, _Align::_Left, [=](_OutputIt _Out) { + return _Write(_STD move(_Out), _Value.substr(size_t{0}, static_cast(_Printed_size))); + }); } // This is the visitor that's used for "simple" replacement fields, @@ -1951,13 +2006,13 @@ struct _Default_arg_formatter { locale _Loc; template - _OutputIt operator()(_Ty _Val) { - return _Write<_CharT>(_Out, _Val); + _OutputIt operator()(_Ty _Val) && { + return _Write<_CharT>(_STD move(_Out), _Val); } - _OutputIt operator()(typename basic_format_arg<_Context>::handle _Handle) { + _OutputIt operator()(typename basic_format_arg<_Context>::handle _Handle) && { basic_format_parse_context<_CharT> _Parse_ctx({}); - basic_format_context<_OutputIt, _CharT> _Format_ctx(_Out, _Args, _Loc); + basic_format_context<_OutputIt, _CharT> _Format_ctx(_STD move(_Out), _Args, _Loc); _Handle.format(_Parse_ctx, _Format_ctx); return _Format_ctx.out(); } @@ -1993,18 +2048,15 @@ struct _Format_handler { explicit _Format_handler( _OutputIt _Out, basic_string_view<_CharT> _Str, basic_format_args<_Context> _Format_args, const locale& _Loc) - : _Parse_context(_Str), _Ctx(_Out, _Format_args, _Loc) {} + : _Parse_context(_Str), _Ctx(_STD move(_Out), _Format_args, _Loc) {} void _On_text(const _CharT* _Begin, const _CharT* _End) { - auto _Size = _End - _Begin; - auto _Out = _Ctx.out(); - _Out = _STD copy_n(_Begin, _Size, _Out); - _Ctx.advance_to(_Out); + _Ctx.advance_to(_RANGES _Copy_unchecked(_Begin, _End, _Ctx.out()).out); } void _On_replacement_field(size_t _Id, const _CharT*) { auto _Arg = _Ctx.arg(_Id); - _Ctx.advance_to(visit_format_arg( + _Ctx.advance_to(_STD visit_format_arg( _Default_arg_formatter<_OutputIt, _CharT>{_Ctx.out(), _Ctx._Get_args(), _Ctx.locale()}, _Arg)); } @@ -2143,7 +2195,7 @@ template _OutputIt> _OutputIt vformat_to( _OutputIt _Out, const string_view _Fmt, const format_args_t, char> _Args) { _Format_handler<_OutputIt, char, basic_format_context<_OutputIt, char>> _Handler( - _Out, _Fmt, _Args, locale::classic()); + _STD move(_Out), _Fmt, _Args, locale::classic()); _Parse_format_string(_Fmt, _Handler); return _Handler._Ctx.out(); } @@ -2152,7 +2204,7 @@ template _OutputIt> _OutputIt vformat_to( _OutputIt _Out, const wstring_view _Fmt, const format_args_t, wchar_t> _Args) { _Format_handler<_OutputIt, wchar_t, basic_format_context<_OutputIt, wchar_t>> _Handler( - _Out, _Fmt, _Args, locale::classic()); + _STD move(_Out), _Fmt, _Args, locale::classic()); _Parse_format_string(_Fmt, _Handler); return _Handler._Ctx.out(); } @@ -2160,7 +2212,8 @@ _OutputIt vformat_to( template _OutputIt> _OutputIt vformat_to(_OutputIt _Out, const locale& _Loc, const string_view _Fmt, const format_args_t, char> _Args) { - _Format_handler<_OutputIt, char, basic_format_context<_OutputIt, char>> _Handler(_Out, _Fmt, _Args, _Loc); + _Format_handler<_OutputIt, char, basic_format_context<_OutputIt, char>> _Handler( + _STD move(_Out), _Fmt, _Args, _Loc); _Parse_format_string(_Fmt, _Handler); return _Handler._Ctx.out(); } @@ -2168,30 +2221,31 @@ _OutputIt vformat_to(_OutputIt _Out, const locale& _Loc, const string_view _Fmt, template _OutputIt> _OutputIt vformat_to(_OutputIt _Out, const locale& _Loc, const wstring_view _Fmt, const format_args_t, wchar_t> _Args) { - _Format_handler<_OutputIt, wchar_t, basic_format_context<_OutputIt, wchar_t>> _Handler(_Out, _Fmt, _Args, _Loc); + _Format_handler<_OutputIt, wchar_t, basic_format_context<_OutputIt, wchar_t>> _Handler( + _STD move(_Out), _Fmt, _Args, _Loc); _Parse_format_string(_Fmt, _Handler); return _Handler._Ctx.out(); } -template +template _OutputIt, class... _Types> _OutputIt format_to(_OutputIt _Out, const string_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_OutputIt, char>; return _STD vformat_to(_STD move(_Out), _Fmt, _STD make_format_args<_Context>(_Args...)); } -template +template _OutputIt, class... _Types> _OutputIt format_to(_OutputIt _Out, const wstring_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_OutputIt, wchar_t>; return _STD vformat_to(_STD move(_Out), _Fmt, _STD make_format_args<_Context>(_Args...)); } -template +template _OutputIt, class... _Types> _OutputIt format_to(_OutputIt _Out, const locale& _Loc, const string_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_OutputIt, char>; return _STD vformat_to(_STD move(_Out), _Loc, _Fmt, _STD make_format_args<_Context>(_Args...)); } -template +template _OutputIt, class... _Types> _OutputIt format_to(_OutputIt _Out, const locale& _Loc, const wstring_view _Fmt, const _Types&... _Args) { using _Context = basic_format_context<_OutputIt, wchar_t>; return _STD vformat_to(_STD move(_Out), _Loc, _Fmt, _STD make_format_args<_Context>(_Args...)); diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 49828d83b0e..b4bb3212eed 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1481,27 +1481,6 @@ struct _NODISCARD _Uninitialized_backout { } }; -#ifdef __cpp_lib_concepts -namespace ranges { - // STRUCT TEMPLATE in_out_result - template - struct in_out_result { - /* [[no_unique_address]] */ _In in; - /* [[no_unique_address]] */ _Out out; - - template <_Convertible_from _IIn, _Convertible_from _OOut> - constexpr operator in_out_result<_IIn, _OOut>() const& { - return {in, out}; - } - - template <_Convertible_from<_In> _IIn, _Convertible_from<_Out> _OOut> - constexpr operator in_out_result<_IIn, _OOut>() && { - return {_STD move(in), _STD move(out)}; - } - }; -} // namespace ranges -#endif // __cpp_lib_concepts - // FUNCTION TEMPLATE _Uninitialized_move_unchecked template _CONSTEXPR20_DYNALLOC _NoThrowFwdIt _Uninitialized_move_unchecked( diff --git a/stl/inc/xutility b/stl/inc/xutility index 728c332e48e..3b70a2969f2 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -4210,6 +4210,55 @@ _FwdIt2 copy(_ExPo&&, _FwdIt1 _First, _FwdIt1 _Last, _FwdIt2 _Dest) noexcept /* } #endif // _HAS_CXX17 +#ifdef __cpp_lib_concepts +namespace ranges { + // CONCEPT _Convertible_from + template + concept _Convertible_from = convertible_to<_From, _To>; + + // STRUCT TEMPLATE in_out_result + template + struct in_out_result { + /* [[no_unique_address]] */ _In in; + /* [[no_unique_address]] */ _Out out; + + template <_Convertible_from _IIn, _Convertible_from _OOut> + constexpr operator in_out_result<_IIn, _OOut>() const& { + return {in, out}; + } + + template <_Convertible_from<_In> _IIn, _Convertible_from<_Out> _OOut> + constexpr operator in_out_result<_IIn, _OOut>() && { + return {_STD move(in), _STD move(out)}; + } + }; + + // ALIAS TEMPLATE copy_result + template + using copy_result = in_out_result<_In, _Out>; + + // 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) { + 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)); + return {_STD move(_Final), _STD move(_Result)}; + } + } + + for (; _First != _Last; ++_First, (void) ++_Result) { + *_Result = *_First; + } + + return {_STD move(_First), _STD move(_Result)}; + } + // clang-format on +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE copy_n template _CONSTEXPR20 _OutIt copy_n(_InIt _First, _Diff _Count_raw, _OutIt _Dest) { @@ -4565,6 +4614,46 @@ _FwdIt fill_n(_ExPo&&, _FwdIt _Dest, _Diff _Count_raw, const _Ty& _Val) noexcept } #endif // _HAS_CXX17 +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::fill_n + class _Fill_n_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _It> + constexpr _It operator()(_It _First, iter_difference_t<_It> _Count, const _Ty& _Value) const { + if (_Count > 0) { + auto _UFirst = _Get_unwrapped_n(_STD move(_First), _Count); + if (!_STD is_constant_evaluated()) { + if constexpr (_Fill_memset_is_safe) { + _Fill_memset(_UFirst, _Value, static_cast(_Count)); + _Seek_wrapped(_First, _UFirst + _Count); // no need to move since _UFirst is a pointer + return _First; + } else if constexpr (_Fill_zero_memset_is_safe) { + if (_Is_all_bits_zero(_Value)) { + _Fill_zero_memset(_UFirst, static_cast(_Count)); + _Seek_wrapped(_First, _UFirst + _Count); // no need to move since _UFirst is a pointer + return _First; + } + } + } + + for (; _Count > 0; ++_UFirst, (void) --_Count) { + *_UFirst = _Value; + } + + _Seek_wrapped(_First, _STD move(_UFirst)); + } + + return _First; + } + }; + + inline constexpr _Fill_n_fn fill_n{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE equal // _Can_memcmp_elements<_Elem1, _Elem2> reports whether `_Elem1 == _Elem2` can be optimized to memcmp. @@ -4766,10 +4855,6 @@ _NODISCARD bool equal(_ExPo&& _Exec, const _FwdIt1 _First1, const _FwdIt1 _Last1 #ifdef __cpp_lib_concepts namespace ranges { - // CONCEPT _Convertible_from - template - concept _Convertible_from = convertible_to<_From, _To>; - // STRUCT TEMPLATE in_in_result template struct in_in_result { diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 1e7c413f80d..423a0d6cb85 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -60,6 +60,48 @@ void throw_helper(const charT* fmt, const Args&... vals) { } } +template +struct move_only_back_inserter { + back_insert_iterator> it; + using difference_type = ptrdiff_t; + + bool moved_from = false; + + move_only_back_inserter() = default; + explicit move_only_back_inserter(basic_string& str) : it{str} {} + + move_only_back_inserter(const move_only_back_inserter&) = delete; + move_only_back_inserter& operator=(const move_only_back_inserter&) = delete; + + move_only_back_inserter(move_only_back_inserter&& other) : it(other.it) { + assert(!exchange(other.moved_from, true)); + } + move_only_back_inserter& operator=(move_only_back_inserter&& other) { + assert(!exchange(other.moved_from, true)); + it = other.it; + moved_from = false; + return *this; + } + + move_only_back_inserter& operator++() { + assert(!moved_from); + ++it; + return *this; + } + + decltype(auto) operator++(int) { + assert(!moved_from); + return it++; + } + + decltype(auto) operator*() { + assert(!moved_from); + return *it; + } +}; + +template +move_only_back_inserter(basic_string&) -> move_only_back_inserter; // tests for format with no format args or replacement fields template @@ -69,6 +111,21 @@ void test_simple_formatting() { vformat_to(back_insert_iterator{output_string}, locale::classic(), STR("f"), make_testing_format_args()); assert(output_string == STR("f")); + output_string.clear(); + format_to(move_only_back_inserter{output_string}, STR("{} {} {} {} {} {} {} {} {}"), true, charT{'a'}, 0, 0u, 0.0, + STR("s"), basic_string_view{STR("sv")}, nullptr, static_cast(nullptr)); + assert(output_string == STR("true a 0 0 0 s sv 0x0 0x0")); + + output_string.clear(); + format_to(move_only_back_inserter{output_string}, STR("{:} {:} {:} {:} {:} {:} {:} {:} {:}"), true, charT{'a'}, 0, + 0u, 0.0, STR("s"), basic_string_view{STR("sv")}, nullptr, static_cast(nullptr)); + assert(output_string == STR("true a 0 0 0 s sv 0x0 0x0")); + + output_string.clear(); + format_to_n(move_only_back_inserter{output_string}, 300, STR("{} {} {} {} {} {} {} {} {}"), true, charT{'a'}, 0, 0u, + 0.0, STR("s"), basic_string_view{STR("sv")}, nullptr, static_cast(nullptr)); + assert(output_string == STR("true a 0 0 0 s sv 0x0 0x0")); + output_string.clear(); vformat_to( back_insert_iterator{output_string}, locale::classic(), STR("format"), make_testing_format_args()); From fb36135c9d062929fe62312344919ba8fde7efa9 Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Wed, 7 Apr 2021 18:53:20 -0700 Subject: [PATCH 36/94] : remove todos, add feature macro (#1811) * remove todos, add feature macro * reorder, enable libcxx format version test * we expect format.version.pass.cpp to pass. Co-authored-by: Stephan T. Lavavej --- stl/inc/format | 2 -- stl/inc/yvals_core.h | 12 +++++++++--- tests/libcxx/expected_results.txt | 3 --- tests/libcxx/skipped_tests.txt | 3 --- .../test.compile.pass.cpp | 14 ++++++++++++++ 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 5feac98cda4..e7148f9a390 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1203,7 +1203,6 @@ private: const unsigned char* _Storage = nullptr; }; -// TODO: test coverage // clang-format off template requires output_iterator<_Out, const _CharT&> @@ -1236,7 +1235,6 @@ public: return _STD move(_OutputIt); } void advance_to(iterator _It) { - // TODO: IDL support probably required _OutputIt = _STD move(_It); } diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index fd88225e9db..5a920d16407 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -165,6 +165,7 @@ // P0608R3 Improving variant's Converting Constructor/Assignment // P0616R0 Using move() In // P0631R8 Math Constants +// P0645R10 Text Formatting // P0646R1 list/forward_list remove()/remove_if()/unique() Return size_type // P0653R2 to_address() // P0655R1 visit() @@ -1226,9 +1227,14 @@ #define __cpp_lib_constexpr_vector 201907L #endif // defined(__cpp_constexpr_dynamic_alloc) && !defined(__clang__) -#define __cpp_lib_destroying_delete 201806L -#define __cpp_lib_endian 201907L -#define __cpp_lib_erase_if 202002L +#define __cpp_lib_destroying_delete 201806L +#define __cpp_lib_endian 201907L +#define __cpp_lib_erase_if 202002L + +#ifdef __cpp_lib_concepts // TRANSITION, GH-395 +#define __cpp_lib_format 201907L +#endif // __cpp_lib_concepts + #define __cpp_lib_generic_unordered_lookup 201811L #define __cpp_lib_int_pow2 202002L #define __cpp_lib_integer_comparison_functions 202002L diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 425d5b39b61..e667375acea 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -301,9 +301,6 @@ std/utilities/variant/variant.variant/variant.assign/T.pass.cpp FAIL std/utilities/variant/variant.variant/variant.ctor/conv.pass.cpp FAIL std/utilities/variant/variant.variant/variant.ctor/T.pass.cpp FAIL -# C++20 P0645R10 " Text Formatting" -std/language.support/support.limits/support.limits.general/format.version.pass.cpp FAIL - # C++20 P0784R7 "More constexpr containers" std/utilities/memory/allocator.traits/allocator.traits.members/construct.pass.cpp FAIL std/utilities/memory/allocator.traits/allocator.traits.members/destroy.pass.cpp FAIL diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index 1b694c48e0d..3ac38303153 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -301,9 +301,6 @@ utilities\variant\variant.variant\variant.assign\T.pass.cpp utilities\variant\variant.variant\variant.ctor\conv.pass.cpp utilities\variant\variant.variant\variant.ctor\T.pass.cpp -# C++20 P0645R10 " Text Formatting" -language.support\support.limits\support.limits.general\format.version.pass.cpp - # C++20 P0784R7 "More constexpr containers" utilities\memory\allocator.traits\allocator.traits.members\construct.pass.cpp utilities\memory\allocator.traits\allocator.traits.members\destroy.pass.cpp 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 1b82f92ce4c..73bf5dfcf86 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 @@ -675,6 +675,20 @@ STATIC_ASSERT(__cpp_lib_filesystem == 201703L); #endif #endif +#if _HAS_CXX20 && !defined(__EDG__) +#ifndef __cpp_lib_format +#error __cpp_lib_format is not defined +#elif __cpp_lib_format != 201907L +#error __cpp_lib_format is not 201907L +#else +STATIC_ASSERT(__cpp_lib_format == 201907L); +#endif +#else +#ifdef __cpp_lib_format +#error __cpp_lib_format is defined +#endif +#endif + #if _HAS_CXX17 #ifndef __cpp_lib_gcd_lcm #error __cpp_lib_gcd_lcm is not defined From 970bff2bde99c2b466584c5b6dc0cc9253c44ed8 Mon Sep 17 00:00:00 2001 From: mnatsuhara <46756417+mnatsuhara@users.noreply.github.com> Date: Thu, 8 Apr 2021 20:13:36 -0700 Subject: [PATCH 37/94] `[time.format]` setup (#1812) Co-authored-by: Stephan T. Lavavej --- stl/inc/chrono | 179 +++++++++++++++ stl/inc/format | 154 +++++++------ stl/inc/queue | 96 ++++---- stl/inc/stack | 96 ++++---- stl/inc/xlocnum | 7 +- tests/std/test.lst | 1 + .../env.lst | 4 + .../test.cpp | 213 ++++++++++++++++++ 8 files changed, 575 insertions(+), 175 deletions(-) create mode 100644 tests/std/tests/P0355R7_calendars_and_time_zones_formatting/env.lst create mode 100644 tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp diff --git a/stl/inc/chrono b/stl/inc/chrono index 2c52c87919c..2cf1fc3694e 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -28,6 +28,11 @@ #include #include #include + +#ifdef __cpp_lib_concepts +#include +#include +#endif // defined(__cpp_lib_concepts) #endif // _HAS_CXX20 #pragma pack(push, _CRT_PACKING) @@ -5171,6 +5176,180 @@ namespace chrono { basic_istream<_CharT, _Traits>& _Is, const _Time_parse_iomanip<_CharT, _Traits, _Alloc, _Parsable>& _Tpi) { return _CHRONO from_stream(_Is, _Tpi._Fmt.c_str(), _Tpi._Tp, _Tpi._Abbrev, _Tpi._Offset); } + +#ifdef __cpp_lib_concepts + // [time.format] + + // clang-format off + template + concept _Chrono_parse_spec_callbacks = _Parse_align_callbacks<_Ty, _CharT> + && _Parse_width_callbacks<_Ty, _CharT> + && _Parse_precision_callbacks<_Ty, _CharT> + && _Width_adapter_callbacks<_Ty, _CharT> + && _Precision_adapter_callbacks<_Ty, _CharT> + && requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln) { + { _At._On_conversion_spec(_CharT{}, _CharT{}) } -> same_as; + { _At._On_lit_char(_CharT{}) } -> same_as; + }; + // clang-format on + + template + struct _Chrono_specs { + _CharT _Lit_char = _CharT{0}; // any char other than {, }, % + char _Modifier = '\0'; // either E or O + char _Type = '\0'; + }; + + template + struct _Chrono_format_specs { + int _Width = 0; + int _Precision = -1; + int _Dynamic_width_index = -1; + 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}}; + // recursive definition in grammar, so could have any number of these with literal chars + vector<_Chrono_specs<_CharT>> _Chrono_specs_list; + }; + + // Model of _Chrono_parse_spec_callbacks that fills a _Chrono_format_specs with the parsed data + template + class _Chrono_specs_setter { + public: + constexpr explicit _Chrono_specs_setter(_Chrono_format_specs<_CharT>& _Specs_) : _Specs(_Specs_) {} + + // same as _Specs_setter + constexpr void _On_align(_Align _Aln) { + _Specs._Alignment = _Aln; + } + + // same as _Specs_setter + constexpr void _On_fill(basic_string_view<_CharT> _Sv) { + if (_Sv.size() > 4) { + _THROW(format_error("Invalid fill (too long).")); + } + + _STD fill(_Specs._Fill, _Specs._Fill + 4, _CharT{}); + _STD copy(_Sv.begin(), _Sv.end(), _Specs._Fill); + } + + constexpr void _On_width(int _Width) { + _Specs._Width = _Width; + } + + constexpr void _On_precision(int _Prec) { + _Specs._Precision = _Prec; + } + + constexpr void _On_conversion_spec(_CharT _Modifier, _CharT _Type) { + // NOTE: same performance note from _Basic_format_specs also applies here + const char _Char_mod = static_cast(_Modifier); + const char _Char_type = static_cast(_Type); + if (_Char_mod != '\0' && _Char_mod != 'E' && _Char_mod != 'O') { + _THROW(format_error("Invalid modifier specification.")); + } + + if (_Type < 0 || _Type > (numeric_limits::max)()) { + _THROW(format_error("Invalid type specification.")); + } + + _Chrono_specs<_CharT> _Conv_spec{._Modifier = _Char_mod, ._Type = _Char_type}; + _Specs._Chrono_specs_list.push_back(_Conv_spec); + } + + constexpr void _On_lit_char(_CharT _Lit_ch) { + _Chrono_specs<_CharT> _Lit_char_spec{._Lit_char = _Lit_ch}; + _Specs._Chrono_specs_list.push_back(_Lit_char_spec); + } + + protected: + _Chrono_format_specs<_CharT>& _Specs; + }; + + template _Callbacks_type> + _NODISCARD constexpr const _CharT* _Parse_conversion_specs( + const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + ++_Begin; // move past % + if (_Begin == _End || *_Begin == '}') { + _THROW(format_error("Invalid format string.")); + } + + _CharT _Mod = '\0'; + _CharT _Ch = *_Begin; + + if (_Ch == 'E' || _Ch == 'O') { // includes modifier + _Mod = _Ch; + ++_Begin; + if (_Begin == _End || *_Begin == '}') { + _THROW(format_error("Invalid format string - missing type after modifier.")); + } + } + + _CharT _Type = *_Begin; + _Callbacks._On_conversion_spec(_Mod, _Type); + + return ++_Begin; + } + + template _Callbacks_type> + _NODISCARD constexpr const _CharT* _Parse_chrono_format_specs( + const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + if (_Begin == _End || *_Begin == '}') { + return _Begin; + } + + _Begin = _Parse_align(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } + + _Begin = _Parse_width(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } + + if (*_Begin == '.') { + _Begin = _Parse_precision(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } + } + + // chrono-spec + while (_Begin != _End && *_Begin != '}') { + if (*_Begin == '%') { // conversion-spec + if (_Begin + 1 == _End) { + _THROW(format_error("Invalid format string - missing type after %")); + } + + _CharT _Next_ch = *(_Begin + 1); + switch (_Next_ch) { + case 'n': + _Callbacks._On_lit_char('\n'); + _Begin += 2; + break; + case 't': + _Callbacks._On_lit_char('\t'); + _Begin += 2; + break; + case '%': + _Callbacks._On_lit_char('%'); + _Begin += 2; + break; + default: // some other type + _Begin = _Parse_conversion_specs(_Begin, _End, _Callbacks); + break; + } + } else { // literal-char + _Callbacks._On_lit_char(*_Begin); + ++_Begin; + } + } + + return _Begin; + } +#endif // __cpp_lib_concepts #endif // _HAS_CXX20 } // namespace chrono diff --git a/stl/inc/format b/stl/inc/format index e7148f9a390..b785cdeee41 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -68,22 +68,6 @@ struct _Auto_id_tag {}; // clang-format off template -concept _Parse_spec_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln, _Sign _Sgn) { - { _At._On_align(_Aln) } -> same_as; - { _At._On_fill(_Sv) } -> same_as; - { _At._On_width(int{}) } -> same_as; - { _At._On_dynamic_width(size_t{}) } -> same_as; - { _At._On_dynamic_width(_Auto_id_tag{}) } -> same_as; - { _At._On_precision(int{}) } -> same_as; - { _At._On_dynamic_precision(size_t{}) } -> same_as; - { _At._On_dynamic_precision(_Auto_id_tag{}) } -> same_as; - { _At._On_sign(_Sgn) } -> same_as; - { _At._On_hash() } -> same_as; - { _At._On_zero() } -> same_as; - { _At._On_localized() } -> same_as; - { _At._On_type(_CharT{}) } -> same_as; -}; -template concept _Parse_arg_id_callbacks = requires(_Ty _At) { { _At._On_auto_id() } -> same_as; { _At._On_manual_id(size_t{}) } -> same_as; @@ -97,6 +81,48 @@ concept _Parse_replacement_field_callbacks = requires(_Ty _At, const _CharT* _Be { _At._On_format_specs(size_t{}, _Begin, _End) } -> same_as; }; +template +concept _Parse_align_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln) { + { _At._On_fill(_Sv) } -> same_as; + { _At._On_align(_Aln) } -> same_as; +}; + +template +concept _Parse_width_callbacks = requires(_Ty _At) { + { _At._On_width(int{}) } -> same_as; +}; + +template +concept _Parse_precision_callbacks = requires(_Ty _At) { + { _At._On_precision(int{}) } -> same_as; +}; + +template +concept _Width_adapter_callbacks = requires(_Ty _At) { + { _At._On_dynamic_width(_Auto_id_tag{}) } -> same_as; + { _At._On_dynamic_width(size_t{}) } -> same_as; +}; + +template +concept _Precision_adapter_callbacks = requires(_Ty _At) { + { _At._On_dynamic_precision(_Auto_id_tag{}) } -> same_as; + { _At._On_dynamic_precision(size_t{}) } -> same_as; +}; + +template +concept _Parse_spec_callbacks = _Parse_align_callbacks<_Ty, _CharT> + && _Parse_width_callbacks<_Ty, _CharT> + && _Parse_precision_callbacks<_Ty, _CharT> + && _Width_adapter_callbacks<_Ty, _CharT> + && _Precision_adapter_callbacks<_Ty, _CharT> + && requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln, _Sign _Sgn) { + { _At._On_sign(_Sgn) } -> same_as; + { _At._On_hash() } -> same_as; + { _At._On_zero() } -> same_as; + { _At._On_localized() } -> same_as; + { _At._On_type(_CharT{}) } -> same_as; +}; + template concept _CharT_or_bool = same_as<_Ty, _CharT> || same_as<_Ty, bool>; @@ -359,7 +385,7 @@ _NODISCARD constexpr const _CharT* _Parse_arg_id( throw format_error("Invalid format string."); } -template _Callbacks_type> +template _Callbacks_type> _NODISCARD constexpr const _CharT* _Parse_align( const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { _STL_INTERNAL_CHECK(_Begin != _End && *_Begin != '}'); @@ -403,9 +429,9 @@ _NODISCARD constexpr const _CharT* _Parse_align( return _Begin; } -// Adapts a type modeling _Parse_spec_callbacks to model _Parse_arg_id_callbacks. +// Adapts a type modeling _Width_adapter_callbacks to model _Parse_arg_id_callbacks. // Used in _Parse_width so that _Parse_arg_id can be used to parse dynamic widths. -template _Callbacks_type> +template _Callbacks_type> struct _Width_adapter { _Callbacks_type& _Callbacks; @@ -419,9 +445,9 @@ struct _Width_adapter { } }; -// Adapts a type modeling _Parse_spec_callbacks to model _Parse_arg_id_callbacks. +// Adapts a type modeling _Precision_adapter_callbacks to model _Parse_arg_id_callbacks. // Used in _Parse_precision so that _Parse_arg_id can be used to parse dynamic precisions. -template _Callbacks_type> +template _Callbacks_type> struct _Precision_adapter { _Callbacks_type& _Callbacks; @@ -454,7 +480,7 @@ struct _Id_adapter { } }; -template _Callbacks_type> +template _Callbacks_type> _NODISCARD constexpr const _CharT* _Parse_width( const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { _STL_INTERNAL_CHECK(_Begin != _End); @@ -475,7 +501,7 @@ _NODISCARD constexpr const _CharT* _Parse_width( return _Begin; } -template _Callbacks_type> +template _Callbacks_type> _NODISCARD constexpr const _CharT* _Parse_precision( const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { ++_Begin; @@ -1280,7 +1306,7 @@ _NODISCARD constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_ } template -_NODISCARD _OutputIt _Write(_OutputIt _Out, monostate) { +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, monostate) { _STL_INTERNAL_CHECK(false); return _Out; } @@ -1294,30 +1320,30 @@ inline constexpr size_t _Format_min_buffer_length = 24; // clang-format off template requires(is_arithmetic_v<_Arithmetic> && !_CharT_or_bool<_Arithmetic, _CharT>) -_NODISCARD _OutputIt _Write(_OutputIt _Out, const _Arithmetic _Value); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _Arithmetic _Value); // clang-format on template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const bool _Value); template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _CharT _Value); template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const void* const _Value); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const void* const _Value); template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT* _Value); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _CharT* _Value); template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const basic_string_view<_CharT> _Value); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const basic_string_view<_CharT> _Value); #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch // clang-format off template requires (is_arithmetic_v<_Arithmetic> && !_CharT_or_bool<_Arithmetic, _CharT>) -_NODISCARD _OutputIt _Write(_OutputIt _Out, const _Arithmetic _Value) { +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _Arithmetic _Value) { // clang-format on // TRANSITION, Reusable buffer array _Buffer; @@ -1328,16 +1354,16 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, const _Arithmetic _Value) { #pragma warning(pop) template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value) { +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const bool _Value) { if constexpr (is_same_v<_CharT, wchar_t>) { - return _Write(_STD move(_Out), _Value ? L"true" : L"false"); + return _Fmt_write(_STD move(_Out), _Value ? L"true" : L"false"); } else { - return _Write(_STD move(_Out), _Value ? "true" : "false"); + return _Fmt_write(_STD move(_Out), _Value ? "true" : "false"); } } template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value) { +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _CharT _Value) { *_Out++ = _Value; return _Out; } @@ -1345,7 +1371,7 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value) { #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const void* const _Value) { +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const void* const _Value) { // TRANSITION, Reusable buffer array _Buffer; const auto [_End, _Ec] = @@ -1358,7 +1384,7 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, const void* const _Value) { #pragma warning(pop) template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _CharT* _Value) { if (!_Value) { throw format_error("String pointer is null."); } @@ -1369,7 +1395,7 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT* _Value) { } template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const basic_string_view<_CharT> _Value) { +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const basic_string_view<_CharT> _Value) { return _RANGES _Copy_unchecked(_Value.begin(), _Value.end(), _STD move(_Out)).out; } @@ -1518,7 +1544,7 @@ _NODISCARD _OutputIt _Write_separated_integer(const char* _Start, const char* co } template -_NODISCARD _OutputIt _Write(_OutputIt _Out, monostate, const _Basic_format_specs<_CharT>&, locale) { +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, monostate, const _Basic_format_specs<_CharT>&, locale) { _STL_INTERNAL_CHECK(false); return _Out; } @@ -1530,30 +1556,31 @@ _NODISCARD _OutputIt _Write_integral( // clang-format off template requires(!_CharT_or_bool<_Integral, _CharT>) -_NODISCARD _OutputIt _Write( +_NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const _Integral _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale); // clang-format on template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const bool _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); +_NODISCARD _OutputIt _Fmt_write( + _OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); template -_NODISCARD _OutputIt _Write( +_NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const _Float _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale); template -_NODISCARD _OutputIt _Write( +_NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const void* const _Value, const _Basic_format_specs<_CharT>& _Specs, locale); template -_NODISCARD _OutputIt _Write( +_NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const _CharT* _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale); template -_NODISCARD _OutputIt _Write( +_NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs, locale); #pragma warning(push) @@ -1566,7 +1593,7 @@ _NODISCARD _OutputIt _Write_integral( throw format_error("integral cannot be stored in charT"); } _Specs._Alt = false; - return _Write(_STD move(_Out), static_cast<_CharT>(_Value), _Specs, _Locale); + return _Fmt_write(_STD move(_Out), static_cast<_CharT>(_Value), _Specs, _Locale); } if (_Specs._Precision != -1) { @@ -1664,14 +1691,14 @@ _NODISCARD _OutputIt _Write_integral( // clang-format off template requires (!_CharT_or_bool<_Integral, _CharT>) -_NODISCARD _OutputIt _Write( +_NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const _Integral _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { // clang-format on return _Write_integral(_STD move(_Out), _Value, _Specs, _Locale); } template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const bool _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { if (_Specs._Type != '\0' && _Specs._Type != 's') { return _Write_integral(_STD move(_Out), static_cast(_Value), _Specs, _Locale); } @@ -1682,21 +1709,22 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, const bool _Value, _Basic_format_spe if (_Specs._Localized) { _Specs._Localized = false; - return _Write(_STD move(_Out), + return _Fmt_write(_STD move(_Out), _Value ? static_cast>(_STD use_facet>(_Locale).truename()) : static_cast>(_STD use_facet>(_Locale).falsename()), _Specs, _Locale); } if constexpr (is_same_v<_CharT, wchar_t>) { - return _Write(_STD move(_Out), _Value ? L"true" : L"false", _Specs, _Locale); + return _Fmt_write(_STD move(_Out), _Value ? L"true" : L"false", _Specs, _Locale); } else { - return _Write(_STD move(_Out), _Value ? "true" : "false", _Specs, _Locale); + return _Fmt_write(_STD move(_Out), _Value ? "true" : "false", _Specs, _Locale); } } template -_NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { +_NODISCARD _OutputIt _Fmt_write( + _OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { if (_Specs._Type != '\0' && _Specs._Type != 'c') { return _Write_integral(_STD move(_Out), _Value, _Specs, _Locale); } @@ -1707,13 +1735,13 @@ _NODISCARD _OutputIt _Write(_OutputIt _Out, const _CharT _Value, _Basic_format_s // Clear the type so that the string_view writer doesn't fail on 'c'. _Specs._Type = '\0'; - return _Write(_STD move(_Out), basic_string_view<_CharT>{&_Value, 1}, _Specs, _Locale); + return _Fmt_write(_STD move(_Out), basic_string_view<_CharT>{&_Value, 1}, _Specs, _Locale); } #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch template -_NODISCARD _OutputIt _Write( +_NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const _Float _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { auto _Sgn = _Specs._Sgn; if (_Sgn == _Sign::_None) { @@ -1910,7 +1938,7 @@ _NODISCARD _OutputIt _Write( #pragma warning(pop) template -_NODISCARD _OutputIt _Write( +_NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const void* const _Value, const _Basic_format_specs<_CharT>& _Specs, locale) { if (_Specs._Type != '\0' && _Specs._Type != 'p') { throw format_error("invalid const void* type"); @@ -1948,17 +1976,17 @@ _NODISCARD _OutputIt _Write( } return _Write_aligned(_STD move(_Out), _Width, _Specs, _Align::_Left, - [=](_OutputIt _Out) { return _Write<_CharT>(_STD move(_Out), _Value); }); + [=](_OutputIt _Out) { return _Fmt_write<_CharT>(_STD move(_Out), _Value); }); } template -_NODISCARD _OutputIt _Write( +_NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const _CharT* _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { - return _Write(_STD move(_Out), basic_string_view<_CharT>{_Value}, _Specs, _Locale); + return _Fmt_write(_STD move(_Out), basic_string_view<_CharT>{_Value}, _Specs, _Locale); } template -_NODISCARD _OutputIt _Write( +_NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs, locale) { if (_Specs._Type != '\0' && _Specs._Type != 's') { throw format_error("invalid string type"); @@ -1987,7 +2015,7 @@ _NODISCARD _OutputIt _Write( } return _Write_aligned(_STD move(_Out), _Printed_size, _Specs, _Align::_Left, [=](_OutputIt _Out) { - return _Write(_STD move(_Out), _Value.substr(size_t{0}, static_cast(_Printed_size))); + return _Fmt_write(_STD move(_Out), _Value.substr(size_t{0}, static_cast(_Printed_size))); }); } @@ -2005,7 +2033,7 @@ struct _Default_arg_formatter { template _OutputIt operator()(_Ty _Val) && { - return _Write<_CharT>(_STD move(_Out), _Val); + return _Fmt_write<_CharT>(_STD move(_Out), _Val); } _OutputIt operator()(typename basic_format_arg<_Context>::handle _Handle) && { @@ -2034,7 +2062,7 @@ struct _Arg_formatter { _OutputIt operator()(_Ty _Val) { _STL_INTERNAL_CHECK(_Specs); _STL_INTERNAL_CHECK(_Ctx); - return _Write(_Ctx->out(), _Val, *_Specs, _Ctx->locale()); + return _Fmt_write(_Ctx->out(), _Val, *_Specs, _Ctx->locale()); } }; diff --git a/stl/inc/queue b/stl/inc/queue index bcd510658e4..6efee980d07 100644 --- a/stl/inc/queue +++ b/stl/inc/queue @@ -22,47 +22,6 @@ _STL_DISABLE_CLANG_WARNINGS _STD_BEGIN // CLASS TEMPLATE queue template > -class queue; - -template -_NODISCARD bool operator==(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { - return _Left.c == _Right.c; -} - -template -_NODISCARD bool operator!=(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { - return _Left.c != _Right.c; -} - -template -_NODISCARD bool operator<(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { - return _Left.c < _Right.c; -} - -template -_NODISCARD bool operator>(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { - return _Left.c > _Right.c; -} - -template -_NODISCARD bool operator<=(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { - return _Left.c <= _Right.c; -} - -template -_NODISCARD bool operator>=(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { - return _Left.c >= _Right.c; -} - -#ifdef __cpp_lib_concepts -template -_NODISCARD compare_three_way_result_t<_Container> operator<=>( - const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { - return _Left.c <=> _Right.c; -} -#endif // __cpp_lib_concepts - -template class queue { public: using value_type = typename _Container::value_type; @@ -149,20 +108,9 @@ public: _Swap_adl(c, _Right.c); } - // clang-format off - friend bool operator== <>(const queue&, const queue&); - friend bool operator!= <>(const queue&, const queue&); - friend bool operator< <>(const queue&, const queue&); - friend bool operator> <>(const queue&, const queue&); - friend bool operator<= <>(const queue&, const queue&); - friend bool operator>= <>(const queue&, const queue&); - // clang-format on - -#ifdef __cpp_lib_concepts - template - friend compare_three_way_result_t<_Container2> operator<=>( - const queue<_Ty2, _Container2>&, const queue<_Ty2, _Container2>&); -#endif // __cpp_lib_concepts + _NODISCARD const _Container& _Get_container() const noexcept { // TRANSITION, VSO-1307828 + return c; + } protected: _Container c{}; @@ -179,6 +127,44 @@ template queue; #endif // _HAS_CXX17 +template +_NODISCARD bool operator==(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { + return _Left._Get_container() == _Right._Get_container(); +} + +template +_NODISCARD bool operator!=(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { + return _Left._Get_container() != _Right._Get_container(); +} + +template +_NODISCARD bool operator<(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { + return _Left._Get_container() < _Right._Get_container(); +} + +template +_NODISCARD bool operator>(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { + return _Left._Get_container() > _Right._Get_container(); +} + +template +_NODISCARD bool operator<=(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { + return _Left._Get_container() <= _Right._Get_container(); +} + +template +_NODISCARD bool operator>=(const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { + return _Left._Get_container() >= _Right._Get_container(); +} + +#ifdef __cpp_lib_concepts +template +_NODISCARD compare_three_way_result_t<_Container> operator<=>( + const queue<_Ty, _Container>& _Left, const queue<_Ty, _Container>& _Right) { + return _Left._Get_container() <=> _Right._Get_container(); +} +#endif // __cpp_lib_concepts + template ::value, int> = 0> void swap(queue<_Ty, _Container>& _Left, queue<_Ty, _Container>& _Right) noexcept(noexcept(_Left.swap(_Right))) { _Left.swap(_Right); diff --git a/stl/inc/stack b/stl/inc/stack index 2cc4d964f56..1ea9f63aec7 100644 --- a/stl/inc/stack +++ b/stl/inc/stack @@ -20,47 +20,6 @@ _STL_DISABLE_CLANG_WARNINGS _STD_BEGIN // CLASS TEMPLATE stack template > -class stack; - -template -_NODISCARD bool operator==(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { - return _Left.c == _Right.c; -} - -template -_NODISCARD bool operator!=(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { - return _Left.c != _Right.c; -} - -template -_NODISCARD bool operator<(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { - return _Left.c < _Right.c; -} - -template -_NODISCARD bool operator>(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { - return _Left.c > _Right.c; -} - -template -_NODISCARD bool operator<=(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { - return _Left.c <= _Right.c; -} - -template -_NODISCARD bool operator>=(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { - return _Left.c >= _Right.c; -} - -#ifdef __cpp_lib_concepts -template -_NODISCARD compare_three_way_result_t<_Container> operator<=>( - const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { - return _Left.c <=> _Right.c; -} -#endif // __cpp_lib_concepts - -template class stack { public: using value_type = typename _Container::value_type; @@ -139,20 +98,9 @@ public: _Swap_adl(c, _Right.c); } - // clang-format off - friend bool operator== <>(const stack&, const stack&); - friend bool operator!= <>(const stack&, const stack&); - friend bool operator< <>(const stack&, const stack&); - friend bool operator> <>(const stack&, const stack&); - friend bool operator<= <>(const stack&, const stack&); - friend bool operator>= <>(const stack&, const stack&); - // clang-format on - -#ifdef __cpp_lib_concepts - template - friend compare_three_way_result_t<_Container2> operator<=>( - const stack<_Ty2, _Container2>&, const stack<_Ty2, _Container2>&); -#endif // __cpp_lib_concepts + _NODISCARD const _Container& _Get_container() const noexcept { // TRANSITION, VSO-1307828 + return c; + } protected: _Container c{}; @@ -169,6 +117,44 @@ template stack; #endif // _HAS_CXX17 +template +_NODISCARD bool operator==(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { + return _Left._Get_container() == _Right._Get_container(); +} + +template +_NODISCARD bool operator!=(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { + return _Left._Get_container() != _Right._Get_container(); +} + +template +_NODISCARD bool operator<(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { + return _Left._Get_container() < _Right._Get_container(); +} + +template +_NODISCARD bool operator>(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { + return _Left._Get_container() > _Right._Get_container(); +} + +template +_NODISCARD bool operator<=(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { + return _Left._Get_container() <= _Right._Get_container(); +} + +template +_NODISCARD bool operator>=(const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { + return _Left._Get_container() >= _Right._Get_container(); +} + +#ifdef __cpp_lib_concepts +template +_NODISCARD compare_three_way_result_t<_Container> operator<=>( + const stack<_Ty, _Container>& _Left, const stack<_Ty, _Container>& _Right) { + return _Left._Get_container() <=> _Right._Get_container(); +} +#endif // __cpp_lib_concepts + template ::value, int> = 0> void swap(stack<_Ty, _Container>& _Left, stack<_Ty, _Container>& _Right) noexcept(noexcept(_Left.swap(_Right))) { _Left.swap(_Right); diff --git a/stl/inc/xlocnum b/stl/inc/xlocnum index 2643b4cbe56..8872e86d8f8 100644 --- a/stl/inc/xlocnum +++ b/stl/inc/xlocnum @@ -253,7 +253,9 @@ protected: }; // STATIC numpunct::id OBJECT -#if !(defined _CRTBLD && defined _BUILDING_SATELLITE_2) // TRANSITION, VSO-578955 +#if !(defined _CRTBLD \ + && (defined _BUILDING_SATELLITE_ATOMIC_WAIT || defined _BUILDING_SATELLITE_1 \ + || defined _BUILDING_SATELLITE_2)) // TRANSITION, VSO-578955 #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdllimport-static-field-def" @@ -265,7 +267,8 @@ __PURE_APPDOMAIN_GLOBAL locale::id numpunct<_Elem>::id; #ifdef __clang__ #pragma clang diagnostic pop #endif // __clang__ -#endif // !(defined _CRTBLD && defined _BUILDING_SATELLITE_2) +#endif // !(defined _CRTBLD && (defined _BUILDING_SATELLITE_ATOMIC_WAIT || defined _BUILDING_SATELLITE_1 || defined + // _BUILDING_SATELLITE_2)) // CLASS TEMPLATE num_get template >> diff --git a/tests/std/test.lst b/tests/std/test.lst index 05572d7cfdc..6db71e01544 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -232,6 +232,7 @@ tests\P0339R6_polymorphic_allocator tests\P0355R7_calendars_and_time_zones_clocks tests\P0355R7_calendars_and_time_zones_dates tests\P0355R7_calendars_and_time_zones_dates_literals +tests\P0355R7_calendars_and_time_zones_formatting tests\P0355R7_calendars_and_time_zones_hms tests\P0355R7_calendars_and_time_zones_io tests\P0355R7_calendars_and_time_zones_time_point_and_durations diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/env.lst b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst 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 new file mode 100644 index 00000000000..7d8e6e10456 --- /dev/null +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -0,0 +1,213 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace chrono; + +#ifndef __clang__ // TRANSITION, LLVM-48606 + +// copied from the string_view tests +template +struct choose_literal; // not defined + +template <> +struct choose_literal { + static constexpr const char* choose(const char* s, const wchar_t*) { + return s; + } +}; + +template <> +struct choose_literal { + static constexpr const wchar_t* choose(const char*, const wchar_t* s) { + return s; + } +}; + +#define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) + +template +struct testing_callbacks { + _Align expected_alignment = _Align::_None; + basic_string_view expected_fill; + int expected_width = -1; + size_t expected_dynamic_width = static_cast(-1); + bool expected_auto_dynamic_width = false; + int expected_precision = -1; + size_t expected_dynamic_precision = static_cast(-1); + bool expected_auto_dynamic_precision = false; + vector<_Chrono_specs>& expected_chrono_specs; + size_t curr_index = 0; + + constexpr void _On_align(_Align aln) { + assert(aln == expected_alignment); + } + constexpr void _On_fill(basic_string_view str_view) { + assert(str_view == expected_fill); + } + constexpr void _On_width(int width) { + assert(width == expected_width); + } + constexpr void _On_dynamic_width(size_t id) { + assert(id == expected_dynamic_width); + } + constexpr void _On_dynamic_width(_Auto_id_tag) { + assert(expected_auto_dynamic_width); + } + constexpr void _On_precision(int pre) { + assert(pre == expected_precision); + } + constexpr void _On_dynamic_precision(size_t id) { + assert(id == expected_dynamic_precision); + } + constexpr void _On_dynamic_precision(_Auto_id_tag) { + assert(expected_auto_dynamic_precision); + } + constexpr 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) { + 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 + ++curr_index; + } +}; + +template +constexpr 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 { + auto end = func(view.data(), view.data() + view.size(), move(callbacks)); + if (expected_end_position != basic_string_view::npos) { + assert(end == view.data() + expected_end_position); + } + assert(!err_expected); + } catch (const format_error&) { + assert(err_expected); + } +} + +template +constexpr bool test_parse_conversion_spec() { + auto parse_conv_spec_fn = _Parse_conversion_specs>; + using view_typ = basic_string_view; + using chrono_spec = _Chrono_specs; + + view_typ s0(TYPED_LITERAL(CharT, "%B")); + view_typ s1(TYPED_LITERAL(CharT, "%Ec")); + view_typ s2(TYPED_LITERAL(CharT, "%Od")); + view_typ s3(TYPED_LITERAL(CharT, "%E")); + view_typ s4(TYPED_LITERAL(CharT, "%")); + view_typ s5(TYPED_LITERAL(CharT, "%}")); + view_typ s6(TYPED_LITERAL(CharT, "%E}")); + + vector v0{{._Type = 'B'}}; + test_parse_helper(parse_conv_spec_fn, s0, false, view_typ::npos, {.expected_chrono_specs = v0}); + + vector v1{{._Modifier = 'E', ._Type = 'c'}}; + test_parse_helper(parse_conv_spec_fn, s1, false, view_typ::npos, {.expected_chrono_specs = v1}); + + 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}); + } + + return true; +} + +template +constexpr 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; + + view_typ s0(TYPED_LITERAL(CharT, "%Oe")); + view_typ s1(TYPED_LITERAL(CharT, "lit")); + view_typ s2(TYPED_LITERAL(CharT, "%H:%M}")); + view_typ s3(TYPED_LITERAL(CharT, "6%H}")); + view_typ s4(TYPED_LITERAL(CharT, "*<6hi")); + view_typ s5(TYPED_LITERAL(CharT, "*^4.4%ymm")); + view_typ s6(TYPED_LITERAL(CharT, "%H%")); + view_typ s7(TYPED_LITERAL(CharT, "%H%}")); + view_typ s8(TYPED_LITERAL(CharT, "A%nB%tC%%D")); + + vector v0{{._Modifier = 'O', ._Type = 'e'}}; + test_parse_helper(parse_chrono_format_specs_fn, s0, false, s0.size(), {.expected_chrono_specs = v0}); + + vector v1{{._Lit_char = 'l'}, {._Lit_char = 'i'}, {._Lit_char = 't'}}; + test_parse_helper(parse_chrono_format_specs_fn, s1, false, s1.size(), {.expected_chrono_specs = v1}); + + vector v2{{._Type = 'H'}, {._Lit_char = ':'}, {._Type = 'M'}}; + test_parse_helper(parse_chrono_format_specs_fn, s2, false, s2.size() - 1, {.expected_chrono_specs = v2}); + + vector v3{{._Type = 'H'}}; + test_parse_helper( + parse_chrono_format_specs_fn, s3, false, s3.size() - 1, {.expected_width = 6, .expected_chrono_specs = v3}); + + vector v8{{._Lit_char = 'A'}, {._Lit_char = '\n'}, {._Lit_char = 'B'}, {._Lit_char = '\t'}, + {._Lit_char = 'C'}, {._Lit_char = '%'}, {._Lit_char = 'D'}}; + test_parse_helper(parse_chrono_format_specs_fn, s8, false, s8.size(), {.expected_chrono_specs = v8}); + + vector v4{{._Lit_char = 'h'}, {._Lit_char = 'i'}}; + test_parse_helper(parse_chrono_format_specs_fn, s4, false, s4.size(), + {.expected_alignment = _Align::_Left, + .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), + .expected_width = 6, + .expected_chrono_specs = v4}); + + vector v5{{._Type = 'y'}, {._Lit_char = 'm'}, {._Lit_char = 'm'}}; + test_parse_helper(parse_chrono_format_specs_fn, s5, false, s5.size(), + {.expected_alignment = _Align::_Center, + .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), + .expected_width = 4, + .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}); + } + + return true; +} + +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__ From e5e024a3c6f0f2b3121df403a7264a2f8425cc4a Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Fri, 9 Apr 2021 16:33:15 -0700 Subject: [PATCH 38/94] Properly handle multibyte encodings in format strings (#1815) * Properly handle multibyte encodings in format strings Fixes #1576. --- stl/inc/algorithm | 63 ---- stl/inc/chrono | 15 +- stl/inc/format | 277 ++++++++++++++++-- stl/inc/xutility | 79 ++++- .../test.cpp | 60 ++-- .../test.cpp | 66 ++++- .../P0645R10_text_formatting_parsing/test.cpp | 62 ++-- 7 files changed, 460 insertions(+), 162 deletions(-) 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(); From 4d7d4f1ebff89cdf502c62d1924849ce14fe5882 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Fri, 9 Apr 2021 17:27:30 -0700 Subject: [PATCH 39/94] : Add header unit test (#1816) Tests format import. --- .../P1502R1_standard_library_header_units/custom_format.py | 2 +- .../P1502R1_standard_library_header_units/custombuild.pl | 2 +- .../std/tests/P1502R1_standard_library_header_units/test.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/std/tests/P1502R1_standard_library_header_units/custom_format.py b/tests/std/tests/P1502R1_standard_library_header_units/custom_format.py index d86678b8fd0..9b77575934a 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/custom_format.py +++ b/tests/std/tests/P1502R1_standard_library_header_units/custom_format.py @@ -29,7 +29,7 @@ def getBuildSteps(self, test, litConfig, shared): 'exception', 'execution', 'filesystem', - # 'format', + 'format', 'forward_list', 'fstream', 'functional', diff --git a/tests/std/tests/P1502R1_standard_library_header_units/custombuild.pl b/tests/std/tests/P1502R1_standard_library_header_units/custombuild.pl index 37dff666bd4..5d07acc08f2 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/custombuild.pl +++ b/tests/std/tests/P1502R1_standard_library_header_units/custombuild.pl @@ -28,7 +28,7 @@ () "exception", "execution", "filesystem", - # "format", + "format", "forward_list", "fstream", "functional", diff --git a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp index 5794c45566a..8f3c55cd999 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp +++ b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp @@ -29,7 +29,7 @@ import ; import ; import ; import ; -// import ; +import ; import ; import ; import ; @@ -305,7 +305,7 @@ int main() { { puts("Testing ."); - puts("(TRANSITION, not yet implemented.)"); + assert(format("{} {}", "testing", "format") == "testing format"); } { From 3708eea432affd5b6709dcea599431c4f074ee40 Mon Sep 17 00:00:00 2001 From: Charlie Barto Date: Fri, 9 Apr 2021 19:14:21 -0700 Subject: [PATCH 40/94] tests from libfmt (#1817) * add many tests from libfmt * remove literal unicode test * notice added. * fix fill with null terminator, call center align tests. * foo -> meow * Update capitalization Co-authored-by: Stephan T. Lavavej * trailing comments + cgmanifest * default should be 1 in chrono. * transition comment for #.2g issue * remove comment about differing behavior. Co-authored-by: Stephan T. Lavavej --- NOTICE.txt | 30 +++ docs/cgmanifest.json | 9 + stl/inc/chrono | 2 + stl/inc/format | 51 +++- .../test.cpp | 255 ++++++++++++++++++ 5 files changed, 338 insertions(+), 9 deletions(-) diff --git a/NOTICE.txt b/NOTICE.txt index 0e92d4512ee..152fcefc111 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -137,3 +137,33 @@ In addition, certain files include the notices provided below. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// + +---------------------- + +// Copyright (c) 2012 - present, Victor Zverovich +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// --- Optional exception to the license --- +// +// As an exception, if, as a result of your compiling your source code, portions +// of this Software are embedded into a machine-executable object form of such +// source code, you may redistribute such embedded portions in such object form +// without including the above copyright and permission notices. diff --git a/docs/cgmanifest.json b/docs/cgmanifest.json index 6e1b6634269..44596388928 100644 --- a/docs/cgmanifest.json +++ b/docs/cgmanifest.json @@ -18,6 +18,15 @@ } } }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/fmtlib/fmt", + "commitHash": "273d8865e31659f69528623754c1742b2819ad26" + } + } + }, { "component": { "type": "git", diff --git a/stl/inc/chrono b/stl/inc/chrono index 19be9514c7b..e5612436d5a 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5207,6 +5207,7 @@ namespace chrono { int _Dynamic_width_index = -1; int _Dynamic_precision_index = -1; _Align _Alignment = _Align::_None; + uint8_t _Fill_length = 1; // At most one codepoint (so one char32_t or four utf-8 char8_t) _CharT _Fill[4 / sizeof(_CharT)] = {_CharT{' '}}; // recursive definition in grammar, so could have any number of these with literal chars @@ -5232,6 +5233,7 @@ namespace chrono { const auto _Pos = _STD _Copy_unchecked(_Sv._Unchecked_begin(), _Sv._Unchecked_end(), _Specs._Fill); _STD fill(_Pos, _STD end(_Specs._Fill), _CharT{}); + _Specs._Fill_length = static_cast(_Sv.size()); } constexpr void _On_width(int _Width) { diff --git a/stl/inc/format b/stl/inc/format index 00857c938fa..39c34580052 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -3,6 +3,37 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NOTE: +// The contents of this header are derived in part from libfmt under the following license: + +// Copyright (c) 2012 - present, Victor Zverovich +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// --- Optional exception to the license --- +// +// As an exception, if, as a result of your compiling your source code, portions +// of this Software are embedded into a machine-executable object form of such +// source code, you may redistribute such embedded portions in such object form +// without including the above copyright and permission notices. + #pragma once #ifndef _FORMAT_ #define _FORMAT_ @@ -781,14 +812,15 @@ void _Parse_format_string(basic_string_view<_CharT> _Format_str, _HandlerT&& _Ha template struct _Basic_format_specs { - int _Width = 0; - int _Precision = -1; - char _Type = '\0'; - _Align _Alignment = _Align::_None; - _Sign _Sgn = _Sign::_None; - bool _Alt = false; - bool _Localized = false; - bool _Leading_zero = false; + int _Width = 0; + int _Precision = -1; + char _Type = '\0'; + _Align _Alignment = _Align::_None; + _Sign _Sgn = _Sign::_None; + bool _Alt = false; + bool _Localized = false; + bool _Leading_zero = false; + uint8_t _Fill_length = 1; // At most one codepoint (so one char32_t or four utf-8 char8_t). _CharT _Fill[4 / sizeof(_CharT)] = {_CharT{' '}}; }; @@ -819,6 +851,7 @@ public: const auto _Pos = _STD _Copy_unchecked(_Sv._Unchecked_begin(), _Sv._Unchecked_end(), _Specs._Fill); _STD fill(_Pos, _STD end(_Specs._Fill), _CharT{}); + _Specs._Fill_length = static_cast(_Sv.size()); } constexpr void _On_sign(_Sign _Sgn) { @@ -1529,7 +1562,7 @@ _NODISCARD _OutputIt _Write_aligned(_OutputIt _Out, const int _Width, const _Bas } } - const basic_string_view<_CharT> _Fill_char{_Specs._Fill, _RANGES find(_Specs._Fill, '\0')}; + const basic_string_view<_CharT> _Fill_char{_Specs._Fill, _Specs._Fill_length}; for (; _Fill_left > 0; --_Fill_left) { _Out = _RANGES copy(_Fill_char, _STD move(_Out)).out; } diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 6beefe52c75..ae8c2251ef3 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -1007,6 +1007,228 @@ void test_multibyte_format_strings() { setlocale(LC_ALL, nullptr); } +// The libfmt_ tests are derived from tests in +// libfmt, Copyright (c) 2012 - present, Victor Zverovich +// See NOTICE.txt for more information. + +template +void libfmt_formatter_test_escape() { + assert(format(STR("{{")) == STR("{")); + assert(format(STR("before {{")) == STR("before {")); + assert(format(STR("{{ after")) == STR("{ after")); + assert(format(STR("before {{ after")) == STR("before { after")); + assert(format(STR("}}")) == STR("}")); + assert(format(STR("before }}")) == STR("before }")); + assert(format(STR("}} after")) == STR("} after")); + assert(format(STR("before }} after")) == STR("before } after")); + assert(format(STR("{{}}")) == STR("{}")); + assert(format(STR("{{{0}}}"), 42) == STR("{42}")); +} + +template +void libfmt_formatter_test_args_in_different_position() { + assert(format(STR("{0}"), 42) == STR("42")); + assert(format(STR("before {0}"), 42) == STR("before 42")); + assert(format(STR("{0} after"), 42) == STR("42 after")); + assert(format(STR("before {0} after"), 42) == STR("before 42 after")); + assert(format(STR("{0} = {1}"), STR("answer"), 42) == STR("answer = 42")); + assert(format(STR("{1} is the {0}"), STR("answer"), 42) == STR("42 is the answer")); + assert(format(STR("{0}{1}{0}"), STR("abra"), STR("cad")) == STR("abracadabra")); +} +template +void libfmt_formatter_test_left_align() { + assert(format(STR("{0:<4}"), 42) == STR("42 ")); + assert(format(STR("{0:<4o}"), 042) == STR("42 ")); + assert(format(STR("{0:<4x}"), 0x42) == STR("42 ")); + assert(format(STR("{0:<5}"), -42) == STR("-42 ")); + assert(format(STR("{0:<5}"), 42u) == STR("42 ")); + assert(format(STR("{0:<5}"), -42l) == STR("-42 ")); + assert(format(STR("{0:<5}"), 42ul) == STR("42 ")); + assert(format(STR("{0:<5}"), -42ll) == STR("-42 ")); + assert(format(STR("{0:<5}"), 42ull) == STR("42 ")); + assert(format(STR("{0:<5}"), -42.0) == STR("-42 ")); + assert(format(STR("{0:<5}"), -42.0l) == STR("-42 ")); + assert(format(STR("{0:<5}"), 'c') == STR("c ")); + assert(format(STR("{0:<5}"), STR("abc")) == STR("abc ")); + assert(format(STR("{0:<8}"), reinterpret_cast(0xface)) == STR("0xface ")); +} + +template +void libfmt_formatter_test_right_align() { + assert(format(STR("{0:>4}"), 42) == STR(" 42")); + assert(format(STR("{0:>4o}"), 042) == STR(" 42")); + assert(format(STR("{0:>4x}"), 0x42) == STR(" 42")); + assert(format(STR("{0:>5}"), -42) == STR(" -42")); + assert(format(STR("{0:>5}"), 42u) == STR(" 42")); + assert(format(STR("{0:>5}"), -42l) == STR(" -42")); + assert(format(STR("{0:>5}"), 42ul) == STR(" 42")); + assert(format(STR("{0:>5}"), -42ll) == STR(" -42")); + assert(format(STR("{0:>5}"), 42ull) == STR(" 42")); + assert(format(STR("{0:>5}"), -42.0) == STR(" -42")); + assert(format(STR("{0:>5}"), -42.0l) == STR(" -42")); + assert(format(STR("{0:>5}"), 'c') == STR(" c")); + assert(format(STR("{0:>5}"), STR("abc")) == STR(" abc")); + assert(format(STR("{0:>8}"), reinterpret_cast(0xface)) == STR(" 0xface")); +} + +template +void libfmt_formatter_test_center_align() { + assert(format(STR("{0:^5}"), 42) == STR(" 42 ")); + assert(format(STR("{0:^5o}"), 042) == STR(" 42 ")); + assert(format(STR("{0:^5x}"), 0x42) == STR(" 42 ")); + assert(format(STR("{0:^5}"), -42) == STR(" -42 ")); + assert(format(STR("{0:^5}"), 42u) == STR(" 42 ")); + assert(format(STR("{0:^5}"), -42l) == STR(" -42 ")); + assert(format(STR("{0:^5}"), 42ul) == STR(" 42 ")); + assert(format(STR("{0:^5}"), -42ll) == STR(" -42 ")); + assert(format(STR("{0:^5}"), 42ull) == STR(" 42 ")); + assert(format(STR("{0:^5}"), -42.0) == STR(" -42 ")); + assert(format(STR("{0:^5}"), -42.0l) == STR(" -42 ")); + assert(format(STR("{0:^5}"), 'c') == STR(" c ")); + assert(format(STR("{0:^6}"), STR("abc")) == STR(" abc ")); + assert(format(STR("{0:^8}"), reinterpret_cast(0xface)) == STR(" 0xface ")); +} + +template +void libfmt_formatter_test_fill() { + throw_helper(STR("{0:{<5}"), 'c'); + throw_helper(STR("{0:{<5}}"), 'c'); + assert(format(STR("{0:*>4}"), 42) == STR("**42")); + assert(format(STR("{0:*>5}"), -42) == STR("**-42")); + assert(format(STR("{0:*>5}"), 42u) == STR("***42")); + assert(format(STR("{0:*>5}"), -42l) == STR("**-42")); + assert(format(STR("{0:*>5}"), 42ul) == STR("***42")); + assert(format(STR("{0:*>5}"), -42ll) == STR("**-42")); + assert(format(STR("{0:*>5}"), 42ull) == STR("***42")); + assert(format(STR("{0:*>5}"), -42.0) == STR("**-42")); + assert(format(STR("{0:*>5}"), -42.0l) == STR("**-42")); + assert(format(STR("{0:*<5}"), 'c') == STR("c****")); + assert(format(STR("{0:*<5}"), STR("abc")) == STR("abc**")); + assert(format(STR("{0:*>8}"), reinterpret_cast(0xface)) == STR("**0xface")); + assert(format(STR("{:}="), STR("meow")) == STR("meow=")); + assert(format(basic_string_view(STR("{:\0>4}"), 6), '*') == basic_string(STR("\0\0\0*"), 4)); + throw_helper(STR("{:\x80\x80\x80\x80\x80>}"), 0); +} + +template +void libfmt_formatter_test_plus_sign() { + assert(format(STR("{0:+}"), 42) == STR("+42")); + assert(format(STR("{0:+}"), -42) == STR("-42")); + assert(format(STR("{0:+}"), 42) == STR("+42")); + assert(format(STR("{0:+}"), 42u) == STR("+42")); // behavior differs from libfmt, but conforms + assert(format(STR("{0:+}"), 42l) == STR("+42")); + assert(format(STR("{0:+}"), 42ul) == STR("+42")); // behavior differs from libfmt, but conforms + assert(format(STR("{0:+}"), 42ll) == STR("+42")); + assert(format(STR("{0:+}"), 42ull) == STR("+42")); // behavior differs from libfmt, but conforms + assert(format(STR("{0:+}"), 42.0) == STR("+42")); + assert(format(STR("{0:+}"), 42.0l) == STR("+42")); + throw_helper(STR("{0:+"), 'c'); + throw_helper(STR("{0:+}"), 'c'); + throw_helper(STR("{0:+}"), STR("abc")); + throw_helper(STR("{0:+}"), reinterpret_cast(0x42)); +} + +template +void libfmt_formatter_test_minus_sign() { + assert(format(STR("{0:-}"), 42) == STR("42")); + assert(format(STR("{0:-}"), -42) == STR("-42")); + assert(format(STR("{0:-}"), 42) == STR("42")); + assert(format(STR("{0:-}"), 42u) == STR("42")); // behavior differs from libfmt, but conforms + assert(format(STR("{0:-}"), 42l) == STR("42")); + assert(format(STR("{0:-}"), 42ul) == STR("42")); // behavior differs from libfmt, but conforms + assert(format(STR("{0:-}"), 42ll) == STR("42")); + assert(format(STR("{0:-}"), 42ull) == STR("42")); // behavior differs from libfmt, but conforms + assert(format(STR("{0:-}"), 42.0) == STR("42")); + assert(format(STR("{0:-}"), 42.0l) == STR("42")); + throw_helper(STR("{0:-"), 'c'); + throw_helper(STR("{0:-}"), 'c'); + throw_helper(STR("{0:-}"), STR("abc")); + throw_helper(STR("{0:-}"), reinterpret_cast(0x42)); +} + +template +void libfmt_formatter_test_space_sign() { + assert(format(STR("{0: }"), 42) == STR(" 42")); + assert(format(STR("{0: }"), -42) == STR("-42")); + assert(format(STR("{0: }"), 42) == STR(" 42")); + assert(format(STR("{0: }"), 42u) == STR(" 42")); // behavior differs from libfmt, but conforms + assert(format(STR("{0: }"), 42l) == STR(" 42")); + assert(format(STR("{0: }"), 42ul) == STR(" 42")); // behavior differs from libfmt, but conforms + assert(format(STR("{0: }"), 42ll) == STR(" 42")); + assert(format(STR("{0: }"), 42ull) == STR(" 42")); // behavior differs from libfmt, but conforms + assert(format(STR("{0: }"), 42.0) == STR(" 42")); + assert(format(STR("{0: }"), 42.0l) == STR(" 42")); + throw_helper(STR("{0: "), 'c'); + throw_helper(STR("{0: }"), 'c'); + throw_helper(STR("{0: }"), STR("abc")); + throw_helper(STR("{0: }"), reinterpret_cast(0x42)); +} + +template +void libfmt_formatter_test_hash_flag() { + assert(format(STR("{0:#}"), 42) == STR("42")); + assert(format(STR("{0:#}"), -42) == STR("-42")); + assert(format(STR("{0:#b}"), 42) == STR("0b101010")); + assert(format(STR("{0:#B}"), 42) == STR("0B101010")); + assert(format(STR("{0:#b}"), -42) == STR("-0b101010")); + assert(format(STR("{0:#x}"), 0x42) == STR("0x42")); + assert(format(STR("{0:#X}"), 0x42) == STR("0X42")); + assert(format(STR("{0:#x}"), -0x42) == STR("-0x42")); + assert(format(STR("{0:#o}"), 0) == STR("0")); + assert(format(STR("{0:#o}"), 042) == STR("042")); + assert(format(STR("{0:#o}"), -042) == STR("-042")); + assert(format(STR("{0:#}"), 42u) == STR("42")); + assert(format(STR("{0:#x}"), 0x42u) == STR("0x42")); + assert(format(STR("{0:#o}"), 042u) == STR("042")); + + assert(format(STR("{0:#}"), -42l) == STR("-42")); + assert(format(STR("{0:#x}"), 0x42l) == STR("0x42")); + assert(format(STR("{0:#x}"), -0x42l) == STR("-0x42")); + assert(format(STR("{0:#o}"), 042l) == STR("042")); + assert(format(STR("{0:#o}"), -042l) == STR("-042")); + assert(format(STR("{0:#}"), 42ul) == STR("42")); + assert(format(STR("{0:#x}"), 0x42ul) == STR("0x42")); + assert(format(STR("{0:#o}"), 042ul) == STR("042")); + + assert(format(STR("{0:#}"), -42ll) == STR("-42")); + assert(format(STR("{0:#x}"), 0x42ll) == STR("0x42")); + assert(format(STR("{0:#x}"), -0x42ll) == STR("-0x42")); + assert(format(STR("{0:#o}"), 042ll) == STR("042")); + assert(format(STR("{0:#o}"), -042ll) == STR("-042")); + assert(format(STR("{0:#}"), 42ull) == STR("42")); + assert(format(STR("{0:#x}"), 0x42ull) == STR("0x42")); + assert(format(STR("{0:#o}"), 042ull) == STR("042")); + + assert(format(STR("{0:#}"), -42.0) == STR("-42.")); // behavior differs from libfmt, but conforms + assert(format(STR("{0:#}"), -42.0l) == STR("-42.")); // behavior differs from libfmt, but conforms + assert(format(STR("{:#.0e}"), 42.0) == STR("4.e+01")); + assert(format(STR("{:#.0f}"), 0.01) == STR("0.")); + // TRANSITION, GH-1818 + // assert(format(STR("{:#.2g}"), 0.5) == STR("0.50")); + assert(format(STR("{:#.0f}"), 0.5) == STR("0.")); + throw_helper(STR("{0:#"), 'c'); + throw_helper(STR("{0:#}"), 'c'); + throw_helper(STR("{0:#}"), STR("abc")); + throw_helper(STR("{0:#}"), reinterpret_cast(0x42)); +} + +template +void libfmt_formatter_test_zero_flag() { + assert(format(STR("{0:0}"), 42) == STR("42")); + assert(format(STR("{0:05}"), -42) == STR("-0042")); + assert(format(STR("{0:05}"), 42u) == STR("00042")); + assert(format(STR("{0:05}"), -42l) == STR("-0042")); + assert(format(STR("{0:05}"), 42ul) == STR("00042")); + assert(format(STR("{0:05}"), -42ll) == STR("-0042")); + 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")); + 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)); +} + int main() { test_simple_formatting(); test_simple_formatting(); @@ -1031,5 +1253,38 @@ int main() { test_multibyte_format_strings(); + libfmt_formatter_test_escape(); + libfmt_formatter_test_escape(); + + libfmt_formatter_test_args_in_different_position(); + libfmt_formatter_test_args_in_different_position(); + + libfmt_formatter_test_left_align(); + libfmt_formatter_test_left_align(); + + libfmt_formatter_test_right_align(); + libfmt_formatter_test_right_align(); + + libfmt_formatter_test_center_align(); + libfmt_formatter_test_center_align(); + + libfmt_formatter_test_fill(); + libfmt_formatter_test_fill(); + + libfmt_formatter_test_plus_sign(); + libfmt_formatter_test_plus_sign(); + + libfmt_formatter_test_minus_sign(); + libfmt_formatter_test_minus_sign(); + + libfmt_formatter_test_space_sign(); + libfmt_formatter_test_space_sign(); + + libfmt_formatter_test_hash_flag(); + libfmt_formatter_test_hash_flag(); + + libfmt_formatter_test_zero_flag(); + libfmt_formatter_test_zero_flag(); + return 0; } From b98d6d00d2c5a44c11fd6f83f151a09e24bbd3cb Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Fri, 9 Apr 2021 20:47:59 -0700 Subject: [PATCH 41/94] : Fix #1818, precision on altenrative general formatting (#1819) * : Fix #1818, precision on altenrative general formatting The fix is to only count significant digits of fixed point numbers. Since the `g` format specifier only outputs fixed point numbers when the exponent would be at least `-4` (and not lower), and we can only reduce significant digits by having leading zeroes, we just need to test against values up to an exponent of -4. Anything below 1 needs an extra zero appended, anything below 0.1 needs 2, etc. * Uncomment test * Diff algo --- stl/inc/format | 18 ++++++++++++++++-- .../test.cpp | 13 +++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 39c34580052..6dd2bd3f2e9 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -2016,9 +2016,23 @@ _NODISCARD _OutputIt _Fmt_write( _Append_decimal = true; } if (_Specs._Type == 'g' || _Specs._Type == 'G') { - _Zeroes_to_append = _Extra_precision + _Precision - static_cast(_Exponent_start - _Buffer_start); + auto _Digits = static_cast(_Exponent_start - _Buffer_start); + if (!_Append_decimal) { - _Zeroes_to_append += 1; + --_Digits; + } + + _Zeroes_to_append = _Extra_precision + _Precision - _Digits; + + // Leading zeroes are not significant if we used fixed point notation. + if (_Exponent_start == _Result.ptr && _STD abs(_Value) < 1.0 && _Value != 0.0) { + for (auto _It = _Buffer_start; _It < _Result.ptr; ++_It) { + if (*_It == '0') { + ++_Zeroes_to_append; + } else if (*_It != '.') { + break; + } + } } } } diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index ae8c2251ef3..600fa9579aa 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -727,6 +727,16 @@ void test_float_specs() { assert(format(STR("{:#.0g}"), Float{0}) == STR("0.")); assert(format(STR("{:#.0G}"), Float{0}) == STR("0.")); + + assert(format(STR("{:#.2g}"), 0.5) == STR("0.50")); + assert(format(STR("{:#.1g}"), 0.5) == STR("0.5")); + assert(format(STR("{:#.3g}"), 0.5) == STR("0.500")); + assert(format(STR("{:#.3g}"), 0.05) == STR("0.0500")); + assert(format(STR("{:#.3g}"), 0.0005) == STR("0.000500")); + assert(format(STR("{:#.3g}"), 0.00005) == STR("5.00e-05")); + assert(format(STR("{:#.2g}"), 0.0999) == STR("0.10")); + assert(format(STR("{:#.3g}"), 0.000470) == STR("0.000470")); + assert(format(STR("{:#} {:#}"), inf, nan) == STR("inf nan")); assert(format(STR("{:#a} {:#a}"), inf, nan) == STR("inf nan")); assert(format(STR("{:#A} {:#A}"), inf, nan) == STR("INF NAN")); @@ -1203,8 +1213,7 @@ void libfmt_formatter_test_hash_flag() { assert(format(STR("{0:#}"), -42.0l) == STR("-42.")); // behavior differs from libfmt, but conforms assert(format(STR("{:#.0e}"), 42.0) == STR("4.e+01")); assert(format(STR("{:#.0f}"), 0.01) == STR("0.")); - // TRANSITION, GH-1818 - // assert(format(STR("{:#.2g}"), 0.5) == STR("0.50")); + assert(format(STR("{:#.2g}"), 0.5) == STR("0.50")); assert(format(STR("{:#.0f}"), 0.5) == STR("0.")); throw_helper(STR("{0:#"), 'c'); throw_helper(STR("{0:#}"), 'c'); From de11c9a3747e812ad6b1f45bf4a50732f3afddfc Mon Sep 17 00:00:00 2001 From: statementreply Date: Sat, 10 Apr 2021 19:28:29 +0800 Subject: [PATCH 42/94] Fix format("{:F}", Inf or NaN) (#1823) --- stl/inc/format | 2 ++ .../P0645R10_text_formatting_formatting/test.cpp | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 6dd2bd3f2e9..fe47c696b0d 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1916,6 +1916,8 @@ _NODISCARD _OutputIt _Fmt_write( _Exponent = 'e'; break; case 'F': + _To_upper = true; + [[fallthrough]]; case 'f': if (_Precision == -1) { _Precision = 6; diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 600fa9579aa..dc86a496582 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -743,7 +743,7 @@ void test_float_specs() { assert(format(STR("{:#e} {:#e}"), inf, nan) == STR("inf nan")); assert(format(STR("{:#E} {:#E}"), inf, nan) == STR("INF NAN")); assert(format(STR("{:#f} {:#f}"), inf, nan) == STR("inf nan")); - assert(format(STR("{:#F} {:#F}"), inf, nan) == STR("inf nan")); + assert(format(STR("{:#F} {:#F}"), inf, nan) == STR("INF NAN")); assert(format(STR("{:#g} {:#g}"), inf, nan) == STR("inf nan")); assert(format(STR("{:#G} {:#G}"), inf, nan) == STR("INF NAN")); @@ -825,6 +825,15 @@ void test_float_specs() { assert(format(STR("{:g}"), value) == STR("1234.53")); assert(format(STR("{:G}"), value) == STR("1234.53")); + + assert(format(STR("{:a} {:a}"), inf, nan) == STR("inf nan")); + assert(format(STR("{:A} {:A}"), inf, nan) == STR("INF NAN")); + assert(format(STR("{:e} {:e}"), inf, nan) == STR("inf nan")); + assert(format(STR("{:E} {:E}"), inf, nan) == STR("INF NAN")); + assert(format(STR("{:f} {:f}"), inf, nan) == STR("inf nan")); + assert(format(STR("{:F} {:F}"), inf, nan) == STR("INF NAN")); + assert(format(STR("{:g} {:g}"), inf, nan) == STR("inf nan")); + assert(format(STR("{:G} {:G}"), inf, nan) == STR("INF NAN")); } template From 817069bab77415e2609114211a1228b3a9c0a797 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 15:56:48 -0700 Subject: [PATCH 43/94] Code review: Adjust newlines. Co-authored-by: Michael Schellenberger Costa --- stl/inc/format | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index fe47c696b0d..fce9d67960e 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -366,9 +366,11 @@ _NODISCARD constexpr const _CharT* _Parse_nonnegative_integer( _Value = _Value * 10 + static_cast(*_Begin - '0'); ++_Begin; } while (_Begin != _End && '0' <= *_Begin && *_Begin <= '9'); + if (_Value > _Max_int) { throw format_error("Number is too big"); } + return _Begin; } @@ -404,12 +406,14 @@ _NODISCARD constexpr const _CharT* _Parse_arg_id( } else { ++_Begin; } + // The format string shouldn't end right after the index number. // The only things permitted after the index are the end of the replacement field ('}') // or the beginning of the format spec (':'). if (_Begin == _End || (*_Begin != '}' && *_Begin != ':')) { throw format_error("Invalid format string."); } + _Callbacks._On_manual_id(_Index); return _Begin; } @@ -474,7 +478,6 @@ _NODISCARD inline int _Code_units_in_next_character(const wchar_t* _First, const // 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) { @@ -503,9 +506,11 @@ _NODISCARD const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, throw format_error("Invalid format string."); } auto _Align_pt = _Begin + _Units; + if (_Align_pt == _End) { _Align_pt = _Begin; } + for (;;) { switch (*_Align_pt) { case '<': @@ -518,6 +523,7 @@ _NODISCARD const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, _Parsed_align = _Align::_Center; break; } + if (_Parsed_align != _Align::_None) { if (_Align_pt != _Begin) { if (*_Begin == '{') { @@ -535,6 +541,7 @@ _NODISCARD const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, } _Align_pt = _Begin; } + return _Begin; } @@ -603,6 +610,7 @@ _NODISCARD constexpr const _CharT* _Parse_width( if (_Begin != _End) { _Begin = _Parse_arg_id(_Begin, _End, _Width_adapter<_CharT, _Callbacks_type>{_Callbacks}); } + if (_Begin == _End || *_Begin != '}') { throw format_error("Invalid format string."); } @@ -636,6 +644,7 @@ _NODISCARD constexpr const _CharT* _Parse_precision( } else { throw format_error("Missing precision specifier."); } + return _Begin; } @@ -709,6 +718,7 @@ _NODISCARD constexpr const _CharT* _Parse_format_specs( if (*_Begin != '}') { _Callbacks._On_type(*_Begin++); } + return _Begin; } @@ -733,6 +743,7 @@ _NODISCARD constexpr const _CharT* _Parse_replacement_field( if (_Begin != _End) { _Ch = *_Begin; } + if (_Ch == '}') { _Handler._On_replacement_field(_Adapter._Arg_id, _Begin); } else if (_Ch == ':') { @@ -744,6 +755,7 @@ _NODISCARD constexpr const _CharT* _Parse_replacement_field( throw format_error("Missing '}' in format string."); } } + return _Begin + 1; } @@ -900,6 +912,7 @@ _NODISCARD constexpr basic_format_arg<_Context> _Get_arg(const _Context& _Ctx, s if (!_Arg) { throw format_error("Argument not found."); } + return _Arg; } @@ -951,6 +964,7 @@ _NODISCARD constexpr int _Get_dynamic_specs(const _FormatArg _Arg) { if (_Val > (numeric_limits::max)()) { throw format_error("Number is too big."); } + return static_cast(_Val); } @@ -1023,6 +1037,7 @@ private: if (_Idx > static_cast((numeric_limits::max)())) { throw format_error("Dynamic width or precision index too large."); } + return static_cast(_Idx); } }; @@ -1522,9 +1537,11 @@ _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _CharT* _Value) { if (!_Value) { throw format_error("String pointer is null."); } + while (*_Value) { *_Out++ = *_Value++; } + return _Out; } @@ -2160,6 +2177,7 @@ _NODISCARD constexpr int _Unicode_width_estimate(const char32_t _Ch) noexcept { } _Result ^= 1; } + return 1; } @@ -2365,6 +2383,7 @@ struct _Format_handler { _Arg._Custom_state.format(_Parse_context, _Ctx); return _Parse_context.begin()._Unwrapped(); } + _Basic_format_specs<_CharT> _Specs; _Specs_checker<_Specs_handler, _Context>> _Handler( _Specs_handler, _Context>(_Specs, _Parse_context, _Ctx), @@ -2373,6 +2392,7 @@ struct _Format_handler { if (_Begin == _End || *_Begin != '}') { throw format_error("Missing '}' in format string."); } + _Ctx.advance_to(_STD visit_format_arg( _Arg_formatter<_OutputIt, _CharT>{._Ctx = _STD addressof(_Ctx), ._Specs = _STD addressof(_Specs)}, _Arg)); return _Begin; @@ -2407,10 +2427,12 @@ struct _Formatter_base { _Specs._Width = _Get_dynamic_specs<_Width_checker>(_FormatCtx.arg(static_cast(_Specs._Dynamic_width_index))); } + if (_Specs._Dynamic_precision_index >= 0) { _Specs._Precision = _Get_dynamic_specs<_Precision_checker>( _FormatCtx.arg(static_cast(_Specs._Dynamic_precision_index))); } + return _STD visit_format_arg( _Arg_formatter{ ._Ctx = _STD addressof(_FormatCtx), ._Specs = _STD addressof(_Specs)}, From 4dc999c477696a744c92ab6ffb9c36d4880aef23 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 15:58:18 -0700 Subject: [PATCH 44/94] Code review: Add const. Co-authored-by: Michael Schellenberger Costa --- stl/inc/format | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index fce9d67960e..cfaf8b73b78 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -174,7 +174,8 @@ public: using const_iterator = typename basic_string_view<_CharT>::const_iterator; using iterator = const_iterator; - constexpr explicit basic_format_parse_context(basic_string_view<_CharT> _Fmt, size_t _Num_args_ = 0) noexcept + constexpr explicit basic_format_parse_context( + const basic_string_view<_CharT> _Fmt, const size_t _Num_args_ = 0) noexcept : _Format_string(_Fmt), _Num_args(_Num_args_) {} basic_format_parse_context(const basic_format_parse_context&) = delete; @@ -556,7 +557,7 @@ struct _Width_adapter { constexpr void _On_auto_id() { _Callbacks._On_dynamic_width(_Auto_id_tag{}); } - constexpr void _On_manual_id(size_t _Id) { + constexpr void _On_manual_id(const size_t _Id) { _Callbacks._On_dynamic_width(_Id); } }; @@ -572,7 +573,7 @@ struct _Precision_adapter { constexpr void _On_auto_id() { _Callbacks._On_dynamic_precision(_Auto_id_tag{}); } - constexpr void _On_manual_id(size_t _Id) { + constexpr void _On_manual_id(const size_t _Id) { _Callbacks._On_dynamic_precision(_Id); } }; @@ -590,7 +591,7 @@ struct _Id_adapter { _Arg_id = _Parse_context.next_arg_id(); _STL_INTERNAL_CHECK(_Arg_id != static_cast(-1)); } - constexpr void _On_manual_id(size_t _Id) { + constexpr void _On_manual_id(const size_t _Id) { _Parse_context.check_arg_id(_Id); _Arg_id = _Id; _STL_INTERNAL_CHECK(_Arg_id != static_cast(-1)); @@ -852,11 +853,11 @@ class _Specs_setter { public: explicit constexpr _Specs_setter(_Basic_format_specs<_CharT>& _Specs_) : _Specs(_Specs_) {} - constexpr void _On_align(_Align _Aln) { + constexpr void _On_align(const _Align _Aln) { _Specs._Alignment = _Aln; } - constexpr void _On_fill(basic_string_view<_CharT> _Sv) { + constexpr void _On_fill(const basic_string_view<_CharT> _Sv) { if (_Sv.size() > _STD size(_Specs._Fill)) { throw format_error("Invalid fill (too long)."); } @@ -866,7 +867,7 @@ public: _Specs._Fill_length = static_cast(_Sv.size()); } - constexpr void _On_sign(_Sign _Sgn) { + constexpr void _On_sign(const _Sign _Sgn) { _Specs._Sgn = _Sgn; } @@ -878,11 +879,11 @@ public: _Specs._Leading_zero = true; } - constexpr void _On_width(int _Width) { + constexpr void _On_width(const int _Width) { _Specs._Width = _Width; } - constexpr void _On_precision(int _Precision) { + constexpr void _On_precision(const int _Precision) { _Specs._Precision = _Precision; } @@ -890,7 +891,7 @@ public: _Specs._Localized = true; } - constexpr void _On_type(_CharT _Type) { + constexpr void _On_type(const _CharT _Type) { // performance note: this could be optimized to one comparison by // first casting to unsigned int (the negative values will be 128-255) if (_Type < 0 || _Type > (numeric_limits::max)()) { @@ -904,7 +905,7 @@ protected: }; template -_NODISCARD constexpr basic_format_arg<_Context> _Get_arg(const _Context& _Ctx, size_t _Arg_id) { +_NODISCARD constexpr basic_format_arg<_Context> _Get_arg(const _Context& _Ctx, const size_t _Arg_id) { // note: while this is parameterized on the _Arg_id type in libfmt we don't // need to do that in std::format because it's only called with either an integer // id or a named id (which we do not support in std::format) @@ -921,7 +922,7 @@ _NODISCARD constexpr basic_format_arg<_Context> _Get_arg(const _Context& _Ctx, s class _Width_checker { public: template - _NODISCARD constexpr unsigned long long operator()(_Ty _Value) { + _NODISCARD constexpr unsigned long long operator()(const _Ty _Value) const { if constexpr (is_integral_v<_Ty>) { if constexpr (is_signed_v<_Ty>) { if (_Value < 0) { @@ -940,7 +941,7 @@ public: class _Precision_checker { public: template - _NODISCARD constexpr unsigned long long operator()(_Ty _Value) { + _NODISCARD constexpr unsigned long long operator()(const _Ty _Value) const { if constexpr (is_integral_v<_Ty>) { if constexpr (is_signed_v<_Ty>) { if (_Value < 0) { @@ -1081,7 +1082,7 @@ private: _Numeric_specs_checker _Numeric_checker; public: - constexpr explicit _Specs_checker(const _Handler& _Handler_inst, _Basic_format_arg_type _Arg_type_) + constexpr explicit _Specs_checker(const _Handler& _Handler_inst, const _Basic_format_arg_type _Arg_type_) : _Handler(_Handler_inst), _Numeric_checker(_Arg_type_) {} // _On_align has no checking, since we don't implement numeric alignments. @@ -2370,13 +2371,13 @@ struct _Format_handler { _Ctx.advance_to(_RANGES _Copy_unchecked(_Begin, _End, _Ctx.out()).out); } - void _On_replacement_field(size_t _Id, const _CharT*) { + void _On_replacement_field(const size_t _Id, const _CharT*) { auto _Arg = _Ctx.arg(_Id); _Ctx.advance_to(_STD visit_format_arg( _Default_arg_formatter<_OutputIt, _CharT>{_Ctx.out(), _Ctx._Get_args(), _Ctx.locale()}, _Arg)); } - const _CharT* _On_format_specs(size_t _Id, const _CharT* _Begin, const _CharT* _End) { + const _CharT* _On_format_specs(const size_t _Id, const _CharT* _Begin, const _CharT* _End) { _Parse_context.advance_to(_Parse_context.begin() + (_Begin - &*_Parse_context.begin())); auto _Arg = _Ctx.arg(_Id); if (_Arg._Active_state == _Basic_format_arg_type::_Custom_type) { From b68c1b466db3abd64ca10f439d5039999ed510dd Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 15:58:57 -0700 Subject: [PATCH 45/94] Code review: Add noexcept. Co-authored-by: Michael Schellenberger Costa --- stl/inc/format | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index cfaf8b73b78..9833f13995c 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1116,7 +1116,7 @@ concept _Has_formatter = requires(const _Ty& _Val, _Context& _Ctx) { // clang-format on template -_NODISCARD constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type); +_NODISCARD constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type) noexcept; // See N4878 [format.arg]/5 // clang-format off @@ -1414,13 +1414,13 @@ public: _OutputIt = _STD move(_It); } - _NODISCARD const basic_format_args& _Get_args() const { + _NODISCARD const basic_format_args& _Get_args() const noexcept { return _Args; } }; template -_NODISCARD constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type) { +_NODISCARD constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type) noexcept { switch (_Type) { case _Basic_format_arg_type::_Int_type: return sizeof(int); @@ -1595,7 +1595,7 @@ _NODISCARD _OutputIt _Write_aligned(_OutputIt _Out, const int _Width, const _Bas } template -_NODISCARD constexpr string_view _Get_integral_prefix(const char _Type, const _Integral _Value) { +_NODISCARD constexpr string_view _Get_integral_prefix(const char _Type, const _Integral _Value) noexcept { switch (_Type) { case 'b': return "0b"sv; From ca581bd62a9746497fc6d081301febbbd87f715c Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 16:03:19 -0700 Subject: [PATCH 46/94] Code review: Fix comment typo. --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 9833f13995c..63a65a5fad0 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1097,7 +1097,7 @@ public: constexpr void _On_zero() { // Note 0 is again not valid for CharT or bool unless a numeric - // presentation type is uesd. + // presentation type is used. _Numeric_checker._Require_numeric_argument(); _Handler::_On_zero(); } From 7a8095831fcb6f34b234026dd9b064840f1c7cdc Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 16:08:18 -0700 Subject: [PATCH 47/94] Code review: Shrink "clang-format off" region. Co-authored-by: Michael Schellenberger Costa --- stl/inc/format | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 63a65a5fad0..7e017151e92 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -153,6 +153,7 @@ concept _Parse_spec_callbacks = _Parse_align_callbacks<_Ty, _CharT> { _At._On_localized() } -> same_as; { _At._On_type(_CharT{}) } -> same_as; }; +// clang-format on template concept _CharT_or_bool = same_as<_Ty, _CharT> || same_as<_Ty, bool>; @@ -160,8 +161,6 @@ concept _CharT_or_bool = same_as<_Ty, _CharT> || same_as<_Ty, bool>; template concept _Format_supported_charT = _Is_any_of_v<_CharT, char, wchar_t>; -// clang-format on - template struct formatter; From 4c2e42e44d2dd02f76335c74c0f5e04741414241 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 16:15:48 -0700 Subject: [PATCH 48/94] Code review: Refactor postincrement. Co-authored-by: Michael Schellenberger Costa --- stl/inc/format | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 7e017151e92..36a58765495 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -716,7 +716,8 @@ _NODISCARD constexpr const _CharT* _Parse_format_specs( // If there's anything remaining we assume it's a type. if (*_Begin != '}') { - _Callbacks._On_type(*_Begin++); + _Callbacks._On_type(*_Begin); + ++_Begin; } return _Begin; From 8953f9c67a0e9946c0ce31a981f35e686b2206e2 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 16:56:18 -0700 Subject: [PATCH 49/94] Code review: Avoid negated condition. Co-authored-by: Michael Schellenberger Costa --- stl/inc/format | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 36a58765495..e04e36bd895 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -401,10 +401,10 @@ _NODISCARD constexpr const _CharT* _Parse_arg_id( // equal to zero (but not '00'). So if _Ch is zero we skip the parsing, leave // _Index set to zero and let the validity checks below ensure that the arg_id // wasn't something like "00", or "023". - if (_Ch != '0') { - _Begin = _Parse_nonnegative_integer(_Begin, _End, _Index); - } else { + if (_Ch == '0') { ++_Begin; + } else { + _Begin = _Parse_nonnegative_integer(_Begin, _End, _Index); } // The format string shouldn't end right after the index number. From 6d414571befb54748f9aad755dc52bfe30d8fe28 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 17:09:25 -0700 Subject: [PATCH 50/94] Code review: Early return in _Count_separators(). Co-authored-by: Michael Schellenberger Costa --- stl/inc/format | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index e04e36bd895..bb0a17516f6 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1655,18 +1655,21 @@ _NODISCARD constexpr bool _In_bounds(const _Ty _Value) { } _NODISCARD inline int _Count_separators(size_t _Digits, const string_view _Groups) { + if (_Groups.empty()) { + return 0; + } + + // Calculate the amount of separators that are going to be inserted based on the groupings of the locale. int _Separators = 0; - if (!_Groups.empty()) { - // Calculate the amount of separators that are going to be inserted based on the groupings of the locale. - auto _Group_it = _Groups.begin(); - while (_Digits > static_cast(*_Group_it)) { - _Digits -= static_cast(*_Group_it); - ++_Separators; - if (_Group_it + 1 != _Groups.end()) { - ++_Group_it; - } + auto _Group_it = _Groups.begin(); + while (_Digits > static_cast(*_Group_it)) { + _Digits -= static_cast(*_Group_it); + ++_Separators; + if (_Group_it + 1 != _Groups.end()) { + ++_Group_it; } } + return _Separators; } From ff0ea8d91055f3cf685a55ff65fa5e51f53ef5b8 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 17:18:20 -0700 Subject: [PATCH 51/94] Code review: Rearrange constexpr variables, use auto. This also uses 10u for clarity. Co-authored-by: Michael Schellenberger Costa --- stl/inc/format | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index bb0a17516f6..6d65ad1da94 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -354,9 +354,11 @@ template _NODISCARD constexpr const _CharT* _Parse_nonnegative_integer( const _CharT* _Begin, const _CharT* _End, unsigned int& _Value) { _STL_INTERNAL_CHECK(_Begin != _End && '0' <= *_Begin && *_Begin <= '9'); - _Value = 0; - constexpr unsigned int _Max_int = static_cast((numeric_limits::max)()); - constexpr unsigned int _Big_int = _Max_int / 10; + + constexpr auto _Max_int = static_cast((numeric_limits::max)()); + constexpr auto _Big_int = _Max_int / 10u; + + _Value = 0; do { if (_Value > _Big_int) { From 97b3e6f5db6383159b50052cac7165264db17584 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 17:21:24 -0700 Subject: [PATCH 52/94] Code review: Move comment. Co-authored-by: Michael Schellenberger Costa --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 6d65ad1da94..157dba0a97e 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -499,8 +499,8 @@ _NODISCARD inline int _Code_units_in_next_character(const wchar_t* _First, const template _Callbacks_type> _NODISCARD const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { - _STL_INTERNAL_CHECK(_Begin != _End && *_Begin != '}'); // align and fill + _STL_INTERNAL_CHECK(_Begin != _End && *_Begin != '}'); auto _Parsed_align = _Align::_None; const int _Units = _Code_units_in_next_character(_Begin, _End, _Getcvt()); From f4fa25f6268d34f83dae17e3efd9768f466e36c4 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 17:29:16 -0700 Subject: [PATCH 53/94] Code review: Remove perf note, compilers optimize this. --- stl/inc/format | 2 -- 1 file changed, 2 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 157dba0a97e..6a8a5f16c7f 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -894,8 +894,6 @@ public: } constexpr void _On_type(const _CharT _Type) { - // performance note: this could be optimized to one comparison by - // first casting to unsigned int (the negative values will be 128-255) if (_Type < 0 || _Type > (numeric_limits::max)()) { throw format_error("Invalid type specification."); } From 6ddd77da135c327028c375ca95c11a3aa81e90af Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 17:35:18 -0700 Subject: [PATCH 54/94] Code review: Explicitly cast from int to unsigned long long. Co-authored-by: Michael Schellenberger Costa --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 6a8a5f16c7f..30d5dbb38a9 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -962,7 +962,7 @@ template _NODISCARD constexpr int _Get_dynamic_specs(const _FormatArg _Arg) { _STL_INTERNAL_STATIC_ASSERT(_Is_any_of_v<_Handler, _Width_checker, _Precision_checker>); const unsigned long long _Val = _STD visit_format_arg(_Handler(), _Arg); - if (_Val > (numeric_limits::max)()) { + if (_Val > static_cast((numeric_limits::max)())) { throw format_error("Number is too big."); } From 176c603051f36795a52d43b411b78359917146bd Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 17:47:04 -0700 Subject: [PATCH 55/94] Code review: Move _Get_format_arg_type_storage_size's definition. --- stl/inc/format | 71 ++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 30d5dbb38a9..165f22187cc 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1116,7 +1116,40 @@ concept _Has_formatter = requires(const _Ty& _Val, _Context& _Ctx) { // clang-format on template -_NODISCARD constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type) noexcept; +_NODISCARD constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type) noexcept { + switch (_Type) { + case _Basic_format_arg_type::_Int_type: + return sizeof(int); + case _Basic_format_arg_type::_UInt_type: + return sizeof(unsigned int); + case _Basic_format_arg_type::_Long_long_type: + return sizeof(long long); + case _Basic_format_arg_type::_ULong_long_type: + return sizeof(unsigned long long); + case _Basic_format_arg_type::_Bool_type: + return sizeof(bool); + case _Basic_format_arg_type::_Char_type: + return sizeof(_CharT); + case _Basic_format_arg_type::_Float_type: + return sizeof(float); + case _Basic_format_arg_type::_Double_type: + return sizeof(double); + case _Basic_format_arg_type::_Long_double_type: + return sizeof(long double); + case _Basic_format_arg_type::_Pointer_type: + return sizeof(void*); + case _Basic_format_arg_type::_CString_type: + return sizeof(const _CharT*); + case _Basic_format_arg_type::_String_type: + return sizeof(basic_string_view<_CharT>); + case _Basic_format_arg_type::_Custom_type: + return sizeof(void*) + sizeof(void (*)()); + case _Basic_format_arg_type::_None: + default: + _STL_INTERNAL_CHECK(false); + return 0; + } +} // See N4878 [format.arg]/5 // clang-format off @@ -1419,42 +1452,6 @@ public: } }; -template -_NODISCARD constexpr size_t _Get_format_arg_type_storage_size(_Basic_format_arg_type _Type) noexcept { - switch (_Type) { - case _Basic_format_arg_type::_Int_type: - return sizeof(int); - case _Basic_format_arg_type::_UInt_type: - return sizeof(unsigned int); - case _Basic_format_arg_type::_Long_long_type: - return sizeof(long long); - case _Basic_format_arg_type::_ULong_long_type: - return sizeof(unsigned long long); - case _Basic_format_arg_type::_Bool_type: - return sizeof(bool); - case _Basic_format_arg_type::_Char_type: - return sizeof(_CharT); - case _Basic_format_arg_type::_Float_type: - return sizeof(float); - case _Basic_format_arg_type::_Double_type: - return sizeof(double); - case _Basic_format_arg_type::_Long_double_type: - return sizeof(long double); - case _Basic_format_arg_type::_Pointer_type: - return sizeof(void*); - case _Basic_format_arg_type::_CString_type: - return sizeof(const _CharT*); - case _Basic_format_arg_type::_String_type: - return sizeof(basic_string_view<_CharT>); - case _Basic_format_arg_type::_Custom_type: - return sizeof(void*) + sizeof(void (*)()); - case _Basic_format_arg_type::_None: - default: - _STL_INTERNAL_CHECK(false); - return 0; - } -} - template _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, monostate) { _STL_INTERNAL_CHECK(false); From 339b67a769116ee8595dda55e5689a37e8ff1c45 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 19:48:23 -0700 Subject: [PATCH 56/94] Update header-units.json. --- stl/inc/header-units.json | 1 + 1 file changed, 1 insertion(+) diff --git a/stl/inc/header-units.json b/stl/inc/header-units.json index f4c76b822dc..4d65c03055e 100644 --- a/stl/inc/header-units.json +++ b/stl/inc/header-units.json @@ -52,6 +52,7 @@ "exception", "execution", "filesystem", + "format", "forward_list", "fstream", "functional", From acd12a752fc777fae61161df6a36aa4850e78344 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 10 Apr 2021 20:08:36 -0700 Subject: [PATCH 57/94] Use the plain concepts_matrix.lst when possible. --- tests/std/tests/P0645R10_text_formatting_args/env.lst | 2 +- tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/std/tests/P0645R10_text_formatting_args/env.lst b/tests/std/tests/P0645R10_text_formatting_args/env.lst index 22f1f0230a4..f3ccc8613c6 100644 --- a/tests/std/tests/P0645R10_text_formatting_args/env.lst +++ b/tests/std/tests/P0645R10_text_formatting_args/env.lst @@ -1,4 +1,4 @@ # Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -RUNALL_INCLUDE ..\strict_winsdk_concepts_matrix.lst +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst b/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst index 22f1f0230a4..f3ccc8613c6 100644 --- a/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst +++ b/tests/std/tests/P0645R10_text_formatting_parse_contexts/env.lst @@ -1,4 +1,4 @@ # Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -RUNALL_INCLUDE ..\strict_winsdk_concepts_matrix.lst +RUNALL_INCLUDE ..\concepts_matrix.lst From ddcba05a4171cff6f044bcb8a94de33cdf64b00a Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sun, 11 Apr 2021 23:52:22 -0700 Subject: [PATCH 58/94] Silence "expression is always false" warning. --- stl/inc/format | 3 +++ 1 file changed, 3 insertions(+) diff --git a/stl/inc/format b/stl/inc/format index 165f22187cc..60e9e50f2dc 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1828,7 +1828,10 @@ _NODISCARD _OutputIt _Write_integral( const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Align::_None; auto _Writer = [&, _End = _End](_OutputIt _Out) { +#pragma warning(push) +#pragma warning(disable : 4296) // '<': expression is always false _Out = _Write_sign(_STD move(_Out), _Specs._Sgn, _Value < _Integral{0}); +#pragma warning(pop) _Out = _RANGES _Copy_unchecked(_Prefix.begin(), _Prefix.end(), _STD move(_Out)).out; if (_Write_leading_zeroes && _Width < _Specs._Width) { _Out = _RANGES fill_n(_STD move(_Out), _Specs._Width - _Width, '0'); From fe384741efc0b48fa8308978a4b4508b2960dd2e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sun, 11 Apr 2021 23:52:25 -0700 Subject: [PATCH 59/94] Refine the xlocnum workaround. --- stl/inc/xlocnum | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/stl/inc/xlocnum b/stl/inc/xlocnum index 8872e86d8f8..9b1c2b0bda7 100644 --- a/stl/inc/xlocnum +++ b/stl/inc/xlocnum @@ -253,9 +253,7 @@ protected: }; // STATIC numpunct::id OBJECT -#if !(defined _CRTBLD \ - && (defined _BUILDING_SATELLITE_ATOMIC_WAIT || defined _BUILDING_SATELLITE_1 \ - || defined _BUILDING_SATELLITE_2)) // TRANSITION, VSO-578955 +#if !defined(_CRTBLD) || defined(CRTDLL2) || !defined(_DLL) || defined(_M_CEE_PURE) // TRANSITION, VSO-578955 #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdllimport-static-field-def" @@ -267,8 +265,7 @@ __PURE_APPDOMAIN_GLOBAL locale::id numpunct<_Elem>::id; #ifdef __clang__ #pragma clang diagnostic pop #endif // __clang__ -#endif // !(defined _CRTBLD && (defined _BUILDING_SATELLITE_ATOMIC_WAIT || defined _BUILDING_SATELLITE_1 || defined - // _BUILDING_SATELLITE_2)) +#endif // !defined(_CRTBLD) || defined(CRTDLL2) || !defined(_DLL) || defined(_M_CEE_PURE) // CLASS TEMPLATE num_get template >> From f4c0d0946d10efb3568039ab9a43b9292fd3c313 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sun, 11 Apr 2021 23:52:28 -0700 Subject: [PATCH 60/94] Defend against macroized signbit, isnan, isinf. --- stl/inc/format | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 60e9e50f2dc..2c7769da91d 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1996,7 +1996,7 @@ _NODISCARD _OutputIt _Fmt_write( auto _Buffer_start = _Buffer.data(); auto _Width = static_cast(_Result.ptr - _Buffer_start); - const auto _Is_negative = _STD signbit(_Value); + const auto _Is_negative = (_STD signbit)(_Value); if (_Is_negative) { // Remove the '-', it will be dealt with directly @@ -2012,7 +2012,7 @@ _NODISCARD _OutputIt _Fmt_write( _Exponent = static_cast(_CSTD toupper(_Exponent)); } - const auto _Is_finite = !_STD isnan(_Value) && !_STD isinf(_Value); + const auto _Is_finite = !(_STD isnan)(_Value) && !(_STD isinf)(_Value); auto _Append_decimal = false; auto _Exponent_start = _Result.ptr; From e1d0081327bfc581e2b3bd6089ea51da9f6ba71c Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sun, 11 Apr 2021 23:52:31 -0700 Subject: [PATCH 61/94] Use _THROW. --- stl/inc/format | 94 +++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 2c7769da91d..baf51f594dc 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -206,7 +206,7 @@ public: // _Next_arg_id < 0 means manual _NODISCARD constexpr size_t next_arg_id() { if (_Next_arg_id < 0) { - throw format_error("Can not switch from manual to automatic indexing"); + _THROW(format_error("Can not switch from manual to automatic indexing")); } return static_cast(_Next_arg_id++); @@ -220,7 +220,7 @@ public: } if (_Next_arg_id > 0) { - throw format_error("Can not switch from automatic to manual indexing"); + _THROW(format_error("Can not switch from automatic to manual indexing")); } _Next_arg_id = -1; } @@ -370,7 +370,7 @@ _NODISCARD constexpr const _CharT* _Parse_nonnegative_integer( } while (_Begin != _End && '0' <= *_Begin && *_Begin <= '9'); if (_Value > _Max_int) { - throw format_error("Number is too big"); + _THROW(format_error("Number is too big")); } return _Begin; @@ -413,14 +413,14 @@ _NODISCARD constexpr const _CharT* _Parse_arg_id( // The only things permitted after the index are the end of the replacement field ('}') // or the beginning of the format spec (':'). if (_Begin == _End || (*_Begin != '}' && *_Begin != ':')) { - throw format_error("Invalid format string."); + _THROW(format_error("Invalid format string.")); } _Callbacks._On_manual_id(_Index); return _Begin; } // This is where we would parse named arg ids if std::format were to support them. - throw format_error("Invalid format string."); + _THROW(format_error("Invalid format string.")); } _NODISCARD inline int _Code_units_in_next_character(const char* _First, const char* _Last, const _Cvtvec& _Cvt) { @@ -505,7 +505,7 @@ _NODISCARD const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, const int _Units = _Code_units_in_next_character(_Begin, _End, _Getcvt()); if (_Units < 0) { // invalid fill character encoding - throw format_error("Invalid format string."); + _THROW(format_error("Invalid format string.")); } auto _Align_pt = _Begin + _Units; @@ -529,7 +529,7 @@ _NODISCARD const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, if (_Parsed_align != _Align::_None) { if (_Align_pt != _Begin) { if (*_Begin == '{') { - throw format_error("invalid fill character '{'"); + _THROW(format_error("invalid fill character '{'")); } _Callbacks._On_fill({_Begin, static_cast(_Align_pt - _Begin)}); _Begin = _Align_pt + 1; @@ -614,7 +614,7 @@ _NODISCARD constexpr const _CharT* _Parse_width( } if (_Begin == _End || *_Begin != '}') { - throw format_error("Invalid format string."); + _THROW(format_error("Invalid format string.")); } ++_Begin; } @@ -641,10 +641,10 @@ _NODISCARD constexpr const _CharT* _Parse_precision( } if (_Begin == _End || *_Begin != '}') { - throw format_error("Invalid format string."); + _THROW(format_error("Invalid format string.")); } } else { - throw format_error("Missing precision specifier."); + _THROW(format_error("Missing precision specifier.")); } return _Begin; @@ -730,7 +730,7 @@ _NODISCARD constexpr const _CharT* _Parse_replacement_field( const _CharT* _Begin, const _CharT* _End, _HandlerT&& _Handler) { ++_Begin; if (_Begin == _End) { - throw format_error("Invalid format string."); + _THROW(format_error("Invalid format string.")); } if (*_Begin == '}') { @@ -752,10 +752,10 @@ _NODISCARD constexpr const _CharT* _Parse_replacement_field( } else if (_Ch == ':') { _Begin = _Handler._On_format_specs(_Adapter._Arg_id, _Begin + 1, _End); if (_Begin == _End || *_Begin != '}') { - throw format_error("Unknown format specifier."); + _THROW(format_error("Unknown format specifier.")); } } else { - throw format_error("Missing '}' in format string."); + _THROW(format_error("Missing '}' in format string.")); } } @@ -774,7 +774,7 @@ const _CharT* _Find_encoded(const _CharT* _First, const _CharT* _Last, const _Ch 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."); + _THROW(format_error("Invalid encoded character in format string.")); } _First += _Units; } @@ -806,7 +806,7 @@ void _Parse_format_string(basic_string_view<_CharT> _Format_str, _HandlerT&& _Ha // the above condition was not met. ++_ClosingCurl; if (_ClosingCurl == _OpeningCurl || *_ClosingCurl != '}') { - throw format_error("Unmatched '}' in format string."); + _THROW(format_error("Unmatched '}' in format string.")); } // We found two closing curls, so output only one of them _Handler._On_text(_Begin, _ClosingCurl); @@ -861,7 +861,7 @@ public: constexpr void _On_fill(const basic_string_view<_CharT> _Sv) { if (_Sv.size() > _STD size(_Specs._Fill)) { - throw format_error("Invalid fill (too long)."); + _THROW(format_error("Invalid fill (too long).")); } const auto _Pos = _STD _Copy_unchecked(_Sv._Unchecked_begin(), _Sv._Unchecked_end(), _Specs._Fill); @@ -895,7 +895,7 @@ public: constexpr void _On_type(const _CharT _Type) { if (_Type < 0 || _Type > (numeric_limits::max)()) { - throw format_error("Invalid type specification."); + _THROW(format_error("Invalid type specification.")); } _Specs._Type = static_cast(_Type); } @@ -911,7 +911,7 @@ _NODISCARD constexpr basic_format_arg<_Context> _Get_arg(const _Context& _Ctx, c // id or a named id (which we do not support in std::format) auto _Arg = _Ctx.arg(_Arg_id); if (!_Arg) { - throw format_error("Argument not found."); + _THROW(format_error("Argument not found.")); } return _Arg; @@ -926,12 +926,12 @@ public: if constexpr (is_integral_v<_Ty>) { if constexpr (is_signed_v<_Ty>) { if (_Value < 0) { - throw format_error("Negative width."); + _THROW(format_error("Negative width.")); } } return static_cast(_Value); } else { - throw format_error("Width is not an integer."); + _THROW(format_error("Width is not an integer.")); } } }; @@ -945,12 +945,12 @@ public: if constexpr (is_integral_v<_Ty>) { if constexpr (is_signed_v<_Ty>) { if (_Value < 0) { - throw format_error("Negative precision."); + _THROW(format_error("Negative precision.")); } } return static_cast(_Value); } else { - throw format_error("Precision is not an integer."); + _THROW(format_error("Precision is not an integer.")); } } }; @@ -963,7 +963,7 @@ _NODISCARD constexpr int _Get_dynamic_specs(const _FormatArg _Arg) { _STL_INTERNAL_STATIC_ASSERT(_Is_any_of_v<_Handler, _Width_checker, _Precision_checker>); const unsigned long long _Val = _STD visit_format_arg(_Handler(), _Arg); if (_Val > static_cast((numeric_limits::max)())) { - throw format_error("Number is too big."); + _THROW(format_error("Number is too big.")); } return static_cast(_Val); @@ -1036,7 +1036,7 @@ private: _NODISCARD static constexpr int _Verify_dynamic_arg_index_in_range(const size_t _Idx) { if (_Idx > static_cast((numeric_limits::max)())) { - throw format_error("Dynamic width or precision index too large."); + _THROW(format_error("Dynamic width or precision index too large.")); } return static_cast(_Idx); @@ -1052,7 +1052,7 @@ public: constexpr void _Require_numeric_argument() const { if (!_Is_arithmetic_fmt_type(_Arg_type)) { - throw format_error("Format specifier requires numeric argument."); + _THROW(format_error("Format specifier requires numeric argument.")); } } @@ -1061,13 +1061,13 @@ public: if (_Is_integral_fmt_type(_Arg_type) && _Arg_type != _Basic_format_arg_type::_Int_type && _Arg_type != _Basic_format_arg_type::_Long_long_type && _Arg_type != _Basic_format_arg_type::_Char_type) { - throw format_error("Format specifier requires signed argument."); + _THROW(format_error("Format specifier requires signed 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."); + _THROW(format_error("Precision not allowed for this argument type.")); } } }; @@ -1533,7 +1533,7 @@ _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const void* const _Value) { template _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _CharT* _Value) { if (!_Value) { - throw format_error("String pointer is null."); + _THROW(format_error("String pointer is null.")); } while (*_Value) { @@ -1750,14 +1750,14 @@ _NODISCARD _OutputIt _Write_integral( _OutputIt _Out, const _Integral _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale) { if (_Specs._Type == 'c') { if (!_In_bounds<_CharT>(_Value)) { - throw format_error("integral cannot be stored in charT"); + _THROW(format_error("integral cannot be stored in charT")); } _Specs._Alt = false; return _Fmt_write(_STD move(_Out), static_cast<_CharT>(_Value), _Specs, _Locale); } if (_Specs._Precision != -1) { - throw format_error("integral cannot have a precision"); + _THROW(format_error("integral cannot have a precision")); } if (_Specs._Sgn == _Sign::_None) { @@ -1787,7 +1787,7 @@ _NODISCARD _OutputIt _Write_integral( _Base = 8; break; default: - throw format_error("invalid integral type"); + _THROW(format_error("invalid integral type")); } // long long -1 representation in binary is 64 bits + sign @@ -1867,7 +1867,7 @@ _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const bool _Value, _Basic_format } if (_Specs._Precision != -1) { - throw format_error("bool cannot have a precision"); + _THROW(format_error("bool cannot have a precision")); } if (_Specs._Localized) { @@ -1893,7 +1893,7 @@ _NODISCARD _OutputIt _Fmt_write( } if (_Specs._Precision != -1) { - throw format_error("charT cannot have a precision"); + _THROW(format_error("charT cannot have a precision")); } // Clear the type so that the string_view writer doesn't fail on 'c'. @@ -1956,7 +1956,7 @@ _NODISCARD _OutputIt _Fmt_write( _Exponent = 'e'; break; default: - throw format_error("invalid floating point type"); + _THROW(format_error("invalid floating point type")); } // Consider the powers of 2 in decimal: @@ -2120,27 +2120,27 @@ template _NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const void* const _Value, const _Basic_format_specs<_CharT>& _Specs, locale) { if (_Specs._Type != '\0' && _Specs._Type != 'p') { - throw format_error("invalid const void* type"); + _THROW(format_error("invalid const void* type")); } if (_Specs._Sgn != _Sign::_None) { - throw format_error("const void* cannot have a sign"); + _THROW(format_error("const void* cannot have a sign")); } if (_Specs._Alt) { - throw format_error("const void* cannot have an alternative representation"); + _THROW(format_error("const void* cannot have an alternative representation")); } if (_Specs._Precision != -1) { - throw format_error("const void* cannot have a precision"); + _THROW(format_error("const void* cannot have a precision")); } if (_Specs._Leading_zero) { - throw format_error("const void* cannot have a leading zero"); + _THROW(format_error("const void* cannot have a leading zero")); } if (_Specs._Localized) { - throw format_error("const void* cannot be localized"); + _THROW(format_error("const void* cannot be localized")); } // Compute the bit width of the pointer (i.e. how many bits it takes to be represented). @@ -2282,23 +2282,23 @@ template _NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs, locale) { if (_Specs._Type != '\0' && _Specs._Type != 's') { - throw format_error("invalid string type"); + _THROW(format_error("invalid string type")); } if (_Specs._Sgn != _Sign::_None) { - throw format_error("string cannot have a sign"); + _THROW(format_error("string cannot have a sign")); } if (_Specs._Alt) { - throw format_error("string cannot have an alternative representation"); + _THROW(format_error("string cannot have an alternative representation")); } if (_Specs._Leading_zero) { - throw format_error("string cannot have a leading zero"); + _THROW(format_error("string cannot have a leading zero")); } if (_Specs._Localized) { - throw format_error("string cannot be localized"); + _THROW(format_error("string cannot be localized")); } if (_Specs._Precision < 0 && _Specs._Width <= 0) { @@ -2394,7 +2394,7 @@ struct _Format_handler { _Arg._Active_state); _Begin = _Parse_format_specs(_Begin, _End, _Handler); if (_Begin == _End || *_Begin != '}') { - throw format_error("Missing '}' in format string."); + _THROW(format_error("Missing '}' in format string.")); } _Ctx.advance_to(_STD visit_format_arg( @@ -2420,7 +2420,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("Missing '}' in format string."); + _THROW(format_error("Missing '}' in format string.")); } return _ParseCtx.begin() + (_It - _ParseCtx._Unchecked_begin()); } From 7e0c874198fcc084187207c0865784366222448d Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sun, 11 Apr 2021 23:52:34 -0700 Subject: [PATCH 62/94] Improve locale testing. --- .../test.cpp | 30 +++++++++++++------ .../P0645R10_text_formatting_parsing/test.cpp | 27 ++++++++++++----- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index dc86a496582..74e02e71b90 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include -#include +#include +#include +#include #include #include #include @@ -810,7 +812,7 @@ void test_float_specs() { assert(format(locale{"en-US"}, STR("{:L}"), nan) == STR("nan")); assert(format(locale{"en-US"}, STR("{:L}"), inf) == STR("inf")); - assert(format(locale{"de_DE"}, STR("{:Lf}"), Float{0}) == STR("0,000000")); + assert(format(locale{"de-DE"}, STR("{:Lf}"), Float{0}) == STR("0,000000")); #endif // !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING // Type @@ -974,7 +976,7 @@ void test_size() { void test_multibyte_format_strings() { { - setlocale(LC_ALL, ".932"); + assert(setlocale(LC_ALL, "ja-JP") != nullptr); 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); @@ -1002,9 +1004,9 @@ void test_multibyte_format_strings() { assert(format("{:\x90}>4.3}", s) == "\x90}\x90}\x93\xfa"sv); } -#ifndef MSVC_INTERNAL_TESTING // TRANSITION, Windows on Contest VMs understand ".UTF-8" codepage +#ifndef MSVC_INTERNAL_TESTING // TRANSITION, the Windows version on Contest VMs doesn't always understand ".UTF-8" { - setlocale(LC_ALL, ".UTF-8"); + assert(setlocale(LC_ALL, ".UTF-8") != nullptr); // 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"); @@ -1014,7 +1016,7 @@ void test_multibyte_format_strings() { } { - setlocale(LC_ALL, ".UTF-8"); + assert(setlocale(LC_ALL, ".UTF-8") != nullptr); try { (void) format("{:\x9f\x8f\x88<10}"sv, 42); // Bad fill character encoding: missing lead byte before \x9f assert(false); @@ -1023,7 +1025,7 @@ void test_multibyte_format_strings() { } #endif // MSVC_INTERNAL_TESTING - setlocale(LC_ALL, nullptr); + assert(setlocale(LC_ALL, "C") != nullptr); } // The libfmt_ tests are derived from tests in @@ -1247,7 +1249,7 @@ void libfmt_formatter_test_zero_flag() { throw_helper(STR("{0:05}"), reinterpret_cast(0x42)); } -int main() { +void test() { test_simple_formatting(); test_simple_formatting(); @@ -1303,6 +1305,16 @@ int main() { libfmt_formatter_test_zero_flag(); libfmt_formatter_test_zero_flag(); +} - return 0; +int main() { + try { + test(); + } catch (const format_error& e) { + printf("format_error: %s\n", e.what()); + assert(false); + } catch (const exception& e) { + printf("exception: %s\n", e.what()); + assert(false); + } } diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index 9a7f62083d7..6b4bf530395 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#include +#include #include +#include +#include #include #include -#include #include using namespace std; @@ -163,7 +164,7 @@ bool test_parse_align() { } else { // test multibyte fill characters { - setlocale(LC_ALL, ".932"); + assert(setlocale(LC_ALL, "ja-JP") != nullptr); test_parse_helper(parse_align_fn, "\x93\xfaX"sv, false, 3, @@ -172,9 +173,9 @@ bool test_parse_align() { {.expected_alignment = _Align::_Center, .expected_fill = "\x92\x6e"sv}); } -#ifndef MSVC_INTERNAL_TESTING // TRANSITION, Windows on Contest VMs understand ".UTF-8" codepage +#ifndef MSVC_INTERNAL_TESTING // TRANSITION, the Windows version on Contest VMs doesn't always understand ".UTF-8" { - setlocale(LC_ALL, ".UTF-8"); + assert(setlocale(LC_ALL, ".UTF-8") != nullptr); // "\xf0\x9f\x8f\x88" is U+1F3C8 AMERICAN FOOTBALL test_parse_helper(parse_align_fn, "\xf0\x9f\x8f\x88(); test_parse_align(); @@ -365,6 +366,16 @@ int main() { test_specs_checker(); static_assert(test_specs_checker()); static_assert(test_specs_checker()); +} - return 0; +int main() { + try { + test(); + } catch (const format_error& e) { + printf("format_error: %s\n", e.what()); + assert(false); + } catch (const exception& e) { + printf("exception: %s\n", e.what()); + assert(false); + } } From fa256ee6cfc7a1738437a3dd3acd5c0d1ee56a23 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sun, 11 Apr 2021 23:52:37 -0700 Subject: [PATCH 63/94] Work around VSO-1309454 (duplicated deduction guides). --- .../std/tests/P1502R1_standard_library_header_units/test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp index 8f3c55cd999..c34aa8835ab 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp +++ b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp @@ -126,7 +126,7 @@ int main() { { puts("Testing ."); -#ifdef MSVC_INTERNAL_TESTING // TRANSITION, VSO-1088552 (deduction guides) +#if 0 // TRANSITION, VSO-1088552 (deduction guides), VSO-1309454 (duplicated deduction guides) constexpr array arr{10, 20, 30, 40, 50}; #else // ^^^ no workaround / workaround vvv constexpr array arr{10, 20, 30, 40, 50}; @@ -581,7 +581,7 @@ int main() { { puts("Testing ."); constexpr int arr[]{11, 0, 22, 0, 33, 0, 44, 0, 55}; -#ifdef MSVC_INTERNAL_TESTING // TRANSITION, VSO-1088552 (deduction guides) +#if 0 // TRANSITION, VSO-1088552 (deduction guides), VSO-1309454 (duplicated deduction guides) assert(ranges::distance(views::filter(arr, [](int x) { return x == 0; })) == 4); static_assert(ranges::distance(views::filter(arr, [](int x) { return x != 0; })) == 5); #else // ^^^ no workaround / workaround vvv From a4a56cb4e0919ec5bfb4c4c8598893585215c3fb Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 12 Apr 2021 16:41:04 -0700 Subject: [PATCH 64/94] Add const. --- stl/inc/chrono | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index e5612436d5a..0fc32ee7efe 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5326,7 +5326,7 @@ namespace chrono { _THROW(format_error("Invalid format string - missing type after %")); } - _CharT _Next_ch = *(_Begin + 1); + const _CharT _Next_ch = *(_Begin + 1); switch (_Next_ch) { case 'n': _Callbacks._On_lit_char('\n'); From f71dadc2836d0845f940e11e452d2f57b3cb38fe Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 12 Apr 2021 16:52:02 -0700 Subject: [PATCH 65/94] Add missing `_WIDEN(_CharT, '.')`. --- stl/inc/xcharconv_ryu.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/xcharconv_ryu.h b/stl/inc/xcharconv_ryu.h index 030d08be992..e045f8e5a37 100644 --- a/stl/inc/xcharconv_ryu.h +++ b/stl/inc/xcharconv_ryu.h @@ -673,7 +673,7 @@ _NODISCARD pair<_CharT*, errc> __d2fixed_buffered_n(_CharT* _First, _CharT* cons } --_Round; const _CharT __c = _Round[0]; - if (__c == '.') { + if (__c == _WIDEN(_CharT, '.')) { _Dot = _Round; } else if (__c == _WIDEN(_CharT, '9')) { _Round[0] = _WIDEN(_CharT, '0'); @@ -1482,7 +1482,7 @@ _NODISCARD pair<_CharT*, errc> __to_chars(_CharT* const _First, _CharT* const _L } else if (_Whole_digits > 0) { // case "17.29" // Performance note: moving digits might not be optimal. _CSTD memmove(_First, _First + 1, static_cast(_Whole_digits) * sizeof(_CharT)); - _First[_Whole_digits] = '.'; + _First[_Whole_digits] = _WIDEN(_CharT, '.'); } else { // case "0.001729" // Performance note: a larger memset() followed by overwriting '.' might be more efficient. _First[0] = _WIDEN(_CharT, '0'); From 12c358155f7361613a16934c87df13be328d3102 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 12 Apr 2021 19:43:15 -0700 Subject: [PATCH 66/94] Add const, verify __d2s_buffered_n() etc. succeeded. --- tests/std/tests/P0067R5_charconv/test.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/std/tests/P0067R5_charconv/test.cpp b/tests/std/tests/P0067R5_charconv/test.cpp index fb80283c18c..8eaf8e2a9b2 100644 --- a/tests/std/tests/P0067R5_charconv/test.cpp +++ b/tests/std/tests/P0067R5_charconv/test.cpp @@ -1090,19 +1090,23 @@ void wchar_tests() { wchar_t buffer[32]; for (const auto& t : double_to_wide_test_cases) { - auto result = __d2s_buffered_n(begin(buffer), end(buffer), t.value, t.fmt); + const auto result = __d2s_buffered_n(begin(buffer), end(buffer), t.value, t.fmt); + assert(result.second == errc{}); const wstring_view sv(t.correct); assert(equal(buffer, result.first, sv.begin(), sv.end())); } for (const auto& t : float_to_wide_test_cases) { - auto result = __f2s_buffered_n(begin(buffer), end(buffer), t.value, t.fmt); + const auto result = __f2s_buffered_n(begin(buffer), end(buffer), t.value, t.fmt); + assert(result.second == errc{}); const wstring_view sv(t.correct); assert(equal(buffer, result.first, sv.begin(), sv.end())); } for (const auto& t : wide_digit_pairs_test_cases) { - auto result = __d2fixed_buffered_n(begin(buffer), end(buffer), t.value, static_cast(t.precision)); + const auto result = + __d2fixed_buffered_n(begin(buffer), end(buffer), t.value, static_cast(t.precision)); + assert(result.second == errc{}); const wstring_view sv(t.correct); assert(equal(buffer, result.first, sv.begin(), sv.end())); } From b81d9eb71db3ba805403e3f1e0f2ce556f6cf392 Mon Sep 17 00:00:00 2001 From: statementreply Date: Tue, 13 Apr 2021 10:58:40 +0800 Subject: [PATCH 67/94] : Assume UTF-8 format strings when execution charset is UTF-8 (#1824) * Assume format strings are always UTF-8 when encoding charset is UTF-8 * Add `/utf-8` tests * Run Shift-JIS tests with `/execution-charset:.932` * Apply code review feedback * constexpr function implies inline * Code review feedback. * Mitigate merge conflicts: Use _THROW. * Mitigate merge conflicts: Check setlocale(), reset to "C". Co-authored-by: Stephan T. Lavavej --- stl/inc/format | 160 +++++++++++------- tests/std/include/test_format_support.hpp | 127 ++++++++++++++ tests/std/test.lst | 2 + .../test.cpp | 29 ---- .../env.lst | 27 +++ .../test.cpp | 65 +++++++ .../P0645R10_text_formatting_parsing/test.cpp | 129 +------------- .../P0645R10_text_formatting_utf8/env.lst | 6 + .../P0645R10_text_formatting_utf8/test.cpp | 64 +++++++ tests/std/tests/concepts_matrix.lst | 2 + 10 files changed, 397 insertions(+), 214 deletions(-) create mode 100644 tests/std/include/test_format_support.hpp create mode 100644 tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/env.lst create mode 100644 tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/test.cpp create mode 100644 tests/std/tests/P0645R10_text_formatting_utf8/env.lst create mode 100644 tests/std/tests/P0645R10_text_formatting_utf8/test.cpp diff --git a/stl/inc/format b/stl/inc/format index fe47c696b0d..8b4eda68de6 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -417,55 +417,85 @@ _NODISCARD constexpr const _CharT* _Parse_arg_id( throw format_error("Invalid format string."); } +_NODISCARD constexpr bool _Is_execution_charset_utf8() { +#pragma warning(push) +#pragma warning(disable : 4309) // 'initializing' : truncation of constant value +#pragma warning(disable : 4566) // character represented by universal-character-name '\u4E00' cannot be represented in + // the current code page +#pragma warning(disable : 6201) // Index '2' is out of valid index range '0' to '1' for possibly stack allocated buffer + // '_Test_char' +#pragma warning(disable : 6239) // ( && ) always evaluates to the result of . + // Did you intend to use the bitwise-and operator? + constexpr char _Test_char[] = "\u4e00"; + return sizeof(_Test_char) == 4 && _Test_char[0] == '\xe4' && _Test_char[1] == '\xb8' && _Test_char[2] == '\x80'; +#pragma warning(pop) +} + +inline constexpr bool _Is_execution_charset_utf8_v = _Is_execution_charset_utf8(); + +_NODISCARD constexpr int _Utf8_code_units_in_next_character( + const char* const _First, const char* const _Last) noexcept { + // Returns a count of the number of UTF-8 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. + 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 _Double_byte_encoding_code_units_in_next_character( + const char* const _First, const char* const _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. + 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; + } +} + _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 constexpr (_Is_execution_charset_utf8_v) { + return _Utf8_code_units_in_next_character(_First, _Last); + } else { + 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 - if (_Ch < 0b1110'0000u) { - // check for non-lead byte or partial 2-byte encoded character - return (_Ch >= 0b1100'0000u && _Len >= 2) ? 2 : -1; - } + case 2: + return _Double_byte_encoding_code_units_in_next_character(_First, _Last, _Cvt); - 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; + case 4: // Assume UTF-8 (as does _Mbrtowc) + return _Utf8_code_units_in_next_character(_First, _Last); } } } @@ -751,20 +781,24 @@ 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 + if constexpr (_Is_execution_charset_utf8_v) { return _Find_unchecked(_First, _Last, _Val); - } + } else { + 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."); + 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; } - _First += _Units; - } - return _First; + return _First; + } } template _HandlerT> @@ -2163,15 +2197,9 @@ _NODISCARD constexpr int _Unicode_width_estimate(const char32_t _Ch) noexcept { return 1; } -_NODISCARD inline int _Estimate_character_width(const char* _Ptr, const int _Units, const _Cvtvec& _Cvt) { +_NODISCARD inline int _Estimate_utf8_character_width(const char* const _Ptr, const int _Units) noexcept { // 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: @@ -2197,6 +2225,22 @@ _NODISCARD inline int _Estimate_character_width(const char* _Ptr, const int _Uni return _Unicode_width_estimate<_Width_estimate_high_intervals>(_Ch); } +_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 constexpr (_Is_execution_charset_utf8_v) { + return _Estimate_utf8_character_width(_Ptr, _Units); + } else { + if (_Cvt._Mbcurmax != 4) { + // not a Unicode encoding; estimate width == number of code units + return _Units; + } + + // assume UTF-8 + return _Estimate_utf8_character_width(_Ptr, _Units); + } +} + _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. diff --git a/tests/std/include/test_format_support.hpp b/tests/std/include/test_format_support.hpp new file mode 100644 index 00000000000..d3bf9b92ac1 --- /dev/null +++ b/tests/std/include/test_format_support.hpp @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#include +#include +#include +#include +#include + +// copied from the string_view tests +template +struct choose_literal; // not defined + +template <> +struct choose_literal { + static constexpr const char* choose(const char* s, const wchar_t*) { + return s; + } +}; + +template <> +struct choose_literal { + static constexpr const wchar_t* choose(const char*, const wchar_t* s) { + return s; + } +}; + +#define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) + +template +struct noop_testing_callbacks { + constexpr void _On_align(std::_Align) {} + constexpr void _On_fill(std::basic_string_view) {} + constexpr void _On_width(unsigned int) {} + constexpr void _On_dynamic_width(std::size_t) {} + constexpr void _On_dynamic_width(std::_Auto_id_tag) {} + constexpr void _On_precision(unsigned int) {} + constexpr void _On_dynamic_precision(std::size_t) {} + constexpr void _On_dynamic_precision(std::_Auto_id_tag) {} + constexpr void _On_sign(std::_Sign) {} + constexpr void _On_hash() {} + constexpr void _On_zero() {} + constexpr void _On_localized() {} + constexpr void _On_type(CharT) {} +}; + +template +struct testing_callbacks { + std::_Align expected_alignment = std::_Align::_None; + std::_Sign expected_sign = std::_Sign::_None; + std::basic_string_view expected_fill; + int expected_width = -1; + std::size_t expected_dynamic_width = static_cast(-1); + bool expected_auto_dynamic_width = false; + int expected_precision = -1; + std::size_t expected_dynamic_precision = static_cast(-1); + bool expected_auto_dynamic_precision = false; + bool expected_hash = false; + bool expected_zero = false; + bool expected_localized = false; + CharT expected_type = '\0'; + + constexpr void _On_align(std::_Align aln) { + assert(aln == expected_alignment); + } + constexpr void _On_fill(std::basic_string_view str_view) { + assert(str_view == expected_fill); + } + constexpr void _On_width(int width) { + assert(width == expected_width); + } + constexpr void _On_dynamic_width(std::size_t id) { + assert(id == expected_dynamic_width); + } + constexpr void _On_dynamic_width(std::_Auto_id_tag) { + assert(expected_auto_dynamic_width); + } + constexpr void _On_precision(int pre) { + assert(pre == expected_precision); + } + constexpr void _On_dynamic_precision(std::size_t id) { + assert(id == expected_dynamic_precision); + } + constexpr void _On_dynamic_precision(std::_Auto_id_tag) { + assert(expected_auto_dynamic_precision); + } + constexpr void _On_sign(std::_Sign sgn) { + assert(sgn == expected_sign); + } + constexpr void _On_hash() { + assert(expected_hash); + } + constexpr void _On_zero() { + assert(expected_zero); + } + constexpr void _On_localized() { + assert(expected_localized); + } + constexpr void _On_type(CharT type) { + assert(type == expected_type); + } +}; +template +testing_callbacks(std::_Align, std::basic_string_view) -> testing_callbacks; + +struct testing_arg_id_callbacks { + constexpr void _On_auto_id() {} + constexpr void _On_manual_id(std::size_t) {} +}; + +template +void test_parse_helper(const CharT* (*func)(const CharT*, const CharT*, callback_type&&), + std::basic_string_view view, bool err_expected = false, + typename std::basic_string_view::size_type expected_end_position = std::basic_string_view::npos, + callback_type&& callbacks = {}) { + try { + auto end = func(view.data(), view.data() + view.size(), std::move(callbacks)); + if (expected_end_position != std::basic_string_view::npos) { + assert(end == view.data() + expected_end_position); + } + assert(!err_expected); + } catch (const std::format_error&) { + assert(err_expected); + } +} diff --git a/tests/std/test.lst b/tests/std/test.lst index 6db71e01544..cba3615d387 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -263,8 +263,10 @@ tests\P0645R10_text_formatting_args tests\P0645R10_text_formatting_custom_formatting tests\P0645R10_text_formatting_death tests\P0645R10_text_formatting_formatting +tests\P0645R10_text_formatting_legacy_text_encoding tests\P0645R10_text_formatting_parse_contexts tests\P0645R10_text_formatting_parsing +tests\P0645R10_text_formatting_utf8 tests\P0660R10_jthread_and_cv_any tests\P0660R10_stop_token tests\P0660R10_stop_token_death diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index dc86a496582..21fda060b4d 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -973,35 +973,6 @@ void test_size() { } 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"); diff --git a/tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/env.lst b/tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/env.lst new file mode 100644 index 00000000000..9aa5f2a5cdb --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/env.lst @@ -0,0 +1,27 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# This is `concepts_matrix.lst` with `/execution-charset:.932` added. +# clang is excluded since it doesn't support non-UTF-8 execution charsets. + +RUNALL_INCLUDE ..\prefix.lst +RUNALL_CROSSLIST +PM_CL="/w14640 /Zc:threadSafeInit- /EHsc /std:c++latest /execution-charset:.932" +RUNALL_CROSSLIST +PM_CL="/MD /D_ITERATOR_DEBUG_LEVEL=0 /permissive- /Zc:noexceptTypes-" +PM_CL="/MD /D_ITERATOR_DEBUG_LEVEL=1 /permissive-" +PM_CL="/MD /D_ITERATOR_DEBUG_LEVEL=0 /permissive- /Zc:char8_t- /Zc:preprocessor" +PM_CL="/MDd /D_ITERATOR_DEBUG_LEVEL=0 /permissive- /Zc:wchar_t-" +PM_CL="/MDd /D_ITERATOR_DEBUG_LEVEL=1 /permissive-" +PM_CL="/MDd /D_ITERATOR_DEBUG_LEVEL=2 /permissive- /fp:except /Zc:preprocessor" +PM_CL="/MT /D_ITERATOR_DEBUG_LEVEL=0 /permissive-" +PM_CL="/MT /D_ITERATOR_DEBUG_LEVEL=0 /permissive- /analyze:only /analyze:autolog-" +PM_CL="/MT /D_ITERATOR_DEBUG_LEVEL=1 /permissive-" +PM_CL="/MTd /D_ITERATOR_DEBUG_LEVEL=0 /permissive- /fp:strict" +PM_CL="/MTd /D_ITERATOR_DEBUG_LEVEL=1 /permissive-" +PM_CL="/MTd /D_ITERATOR_DEBUG_LEVEL=2 /permissive" +PM_CL="/MTd /D_ITERATOR_DEBUG_LEVEL=2 /permissive- /analyze:only /analyze:autolog-" +PM_CL="/permissive- /Za /MD" +PM_CL="/permissive- /Za /MDd" +# PM_CL="/permissive- /BE /c /MD" +# PM_CL="/permissive- /BE /c /MTd" diff --git a/tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/test.cpp b/tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/test.cpp new file mode 100644 index 00000000000..af57f610fc3 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/test.cpp @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +#include "test_format_support.hpp" + +using namespace std; + +void test_multibyte_format_strings() { + { + assert(setlocale(LC_ALL, ".932") != nullptr); + 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); + } + + assert(setlocale(LC_ALL, "C") != nullptr); +} + +void test_parse_align() { + auto parse_align_fn = _Parse_align>; + + { + assert(setlocale(LC_ALL, ".932") != nullptr); + 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}); + } + + assert(setlocale(LC_ALL, "C") != nullptr); +} + +int main() { + test_multibyte_format_strings(); + test_parse_align(); +} diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index 9a7f62083d7..966f04a1a8b 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -8,124 +8,9 @@ #include #include -using namespace std; - -// copied from the string_view tests -template -struct choose_literal; // not defined - -template <> -struct choose_literal { - static constexpr const char* choose(const char* s, const wchar_t*) { - return s; - } -}; - -template <> -struct choose_literal { - static constexpr const wchar_t* choose(const char*, const wchar_t* s) { - return s; - } -}; - -#define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) +#include "test_format_support.hpp" -template -struct noop_testing_callbacks { - constexpr void _On_align(_Align) {} - constexpr void _On_fill(basic_string_view) {} - constexpr void _On_width(unsigned int) {} - constexpr void _On_dynamic_width(size_t) {} - constexpr void _On_dynamic_width(_Auto_id_tag) {} - constexpr void _On_precision(unsigned int) {} - constexpr void _On_dynamic_precision(size_t) {} - constexpr void _On_dynamic_precision(_Auto_id_tag) {} - constexpr void _On_sign(_Sign) {} - constexpr void _On_hash() {} - constexpr void _On_zero() {} - constexpr void _On_localized() {} - constexpr void _On_type(CharT) {} -}; - -template -struct testing_callbacks { - _Align expected_alignment = _Align::_None; - _Sign expected_sign = _Sign::_None; - basic_string_view expected_fill; - int expected_width = -1; - size_t expected_dynamic_width = static_cast(-1); - bool expected_auto_dynamic_width = false; - int expected_precision = -1; - size_t expected_dynamic_precision = static_cast(-1); - bool expected_auto_dynamic_precision = false; - bool expected_hash = false; - bool expected_zero = false; - bool expected_localized = false; - CharT expected_type = '\0'; - - constexpr void _On_align(_Align aln) { - assert(aln == expected_alignment); - } - constexpr void _On_fill(basic_string_view str_view) { - assert(str_view == expected_fill); - } - constexpr void _On_width(int width) { - assert(width == expected_width); - } - constexpr void _On_dynamic_width(size_t id) { - assert(id == expected_dynamic_width); - } - constexpr void _On_dynamic_width(_Auto_id_tag) { - assert(expected_auto_dynamic_width); - } - constexpr void _On_precision(int pre) { - assert(pre == expected_precision); - } - constexpr void _On_dynamic_precision(size_t id) { - assert(id == expected_dynamic_precision); - } - constexpr void _On_dynamic_precision(_Auto_id_tag) { - assert(expected_auto_dynamic_precision); - } - constexpr void _On_sign(_Sign sgn) { - assert(sgn == expected_sign); - } - constexpr void _On_hash() { - assert(expected_hash); - } - constexpr void _On_zero() { - assert(expected_zero); - } - constexpr void _On_localized() { - assert(expected_localized); - } - constexpr void _On_type(CharT type) { - assert(type == expected_type); - } -}; -template -testing_callbacks(_Align, basic_string_view) -> testing_callbacks; - -struct testing_arg_id_callbacks { - constexpr void _On_auto_id() {} - constexpr void _On_manual_id(size_t) {} -}; - -template -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 { - auto end = func(view.data(), view.data() + view.size(), move(callbacks)); - if (expected_end_position != basic_string_view::npos) { - assert(end == view.data() + expected_end_position); - } - assert(!err_expected); - } catch (const format_error&) { - assert(err_expected); - } -} +using namespace std; template bool test_parse_align() { @@ -162,16 +47,6 @@ bool test_parse_align() { } } 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"); diff --git a/tests/std/tests/P0645R10_text_formatting_utf8/env.lst b/tests/std/tests/P0645R10_text_formatting_utf8/env.lst new file mode 100644 index 00000000000..42da0946d2d --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_utf8/env.lst @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst +RUNALL_CROSSLIST +PM_CL="/utf-8" diff --git a/tests/std/tests/P0645R10_text_formatting_utf8/test.cpp b/tests/std/tests/P0645R10_text_formatting_utf8/test.cpp new file mode 100644 index 00000000000..0cf1ca3d9c4 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_utf8/test.cpp @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +#include "test_format_support.hpp" + +using namespace std; + +void test_multibyte_format_strings() { + { + // 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); + } + + { + try { + (void) format("{:\x9f\x8f\x88<10}"sv, 42); // Bad fill character encoding: missing lead byte before \x9f + assert(false); + } catch (const format_error&) { + } + } +} + +void test_parse_align() { + auto parse_align_fn = _Parse_align>; + + { + // "\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}); + } +} + +void run_tests() { + test_multibyte_format_strings(); + test_parse_align(); +} + +int main() { + run_tests(); + + assert(setlocale(LC_ALL, ".1252") != nullptr); + run_tests(); + + assert(setlocale(LC_ALL, ".932") != nullptr); + run_tests(); + +#ifndef MSVC_INTERNAL_TESTING // TRANSITION, Windows on Contest VMs understand ".UTF-8" codepage + assert(setlocale(LC_ALL, ".UTF-8") != nullptr); + run_tests(); +#endif +} diff --git a/tests/std/tests/concepts_matrix.lst b/tests/std/tests/concepts_matrix.lst index 5810e10f464..d35c3015ad0 100644 --- a/tests/std/tests/concepts_matrix.lst +++ b/tests/std/tests/concepts_matrix.lst @@ -1,6 +1,8 @@ # Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# When updating this file, also update tests\P0645R10_text_formatting_legacy_text_encoding\env.lst to match + RUNALL_INCLUDE .\prefix.lst RUNALL_CROSSLIST PM_CL="/w14640 /Zc:threadSafeInit- /EHsc /std:c++latest" From 7146857174ccca8e3583c7400b29db732f545fa2 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 12 Apr 2021 20:45:57 -0700 Subject: [PATCH 68/94] Include more headers. --- stl/inc/format | 2 ++ .../tests/P0645R10_text_formatting_custom_formatting/test.cpp | 1 + tests/std/tests/P0645R10_text_formatting_formatting/test.cpp | 1 + tests/std/tests/P0645R10_text_formatting_parsing/test.cpp | 1 + 4 files changed, 5 insertions(+) diff --git a/stl/inc/format b/stl/inc/format index 31874176023..d2bfb67208d 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -46,9 +46,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include diff --git a/tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp index 211956f947c..a087e83d50a 100644 --- a/tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_custom_formatting/test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index e9d39468e59..4b22d8a082d 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index 49faaf343e0..8b90053be26 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "test_format_support.hpp" From c7d570c9f9ce285796f4d3d6c2ea6733d98feff9 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 12 Apr 2021 21:01:32 -0700 Subject: [PATCH 69/94] Rename integral to Integral to avoid shadowing the concept. --- .../test.cpp | 120 +++++++++--------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 4b22d8a082d..5f3b28a6a8d 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -477,97 +477,97 @@ void test_fill_and_align() { assert(tester() == STR("*AB**")); } -template +template void test_integral_specs() { - assert(format(STR("{:}"), integral{0}) == STR("0")); + assert(format(STR("{:}"), Integral{0}) == STR("0")); // Sign - assert(format(STR("{: }"), integral{0}) == STR(" 0")); - assert(format(STR("{:+}"), integral{0}) == STR("+0")); - assert(format(STR("{:-}"), integral{0}) == STR("0")); - - if constexpr (is_signed_v) { - assert(format(STR("{: }"), integral{-1}) == STR("-1")); - assert(format(STR("{:+}"), integral{-1}) == STR("-1")); - assert(format(STR("{:-}"), integral{-1}) == STR("-1")); + assert(format(STR("{: }"), Integral{0}) == STR(" 0")); + assert(format(STR("{:+}"), Integral{0}) == STR("+0")); + assert(format(STR("{:-}"), Integral{0}) == STR("0")); + + if constexpr (is_signed_v) { + assert(format(STR("{: }"), Integral{-1}) == STR("-1")); + assert(format(STR("{:+}"), Integral{-1}) == STR("-1")); + assert(format(STR("{:-}"), Integral{-1}) == STR("-1")); } - assert(format(STR("{: 3}"), integral{1}) == STR(" 1")); - assert(format(STR("{:+3}"), integral{1}) == STR(" +1")); - assert(format(STR("{:-3}"), integral{1}) == STR(" 1")); + assert(format(STR("{: 3}"), Integral{1}) == STR(" 1")); + assert(format(STR("{:+3}"), Integral{1}) == STR(" +1")); + assert(format(STR("{:-3}"), Integral{1}) == STR(" 1")); // Alternate form - assert(format(STR("{:#}"), integral{0}) == STR("0")); - assert(format(STR("{:#d}"), integral{0}) == STR("0")); - assert(format(STR("{:#c}"), integral{'a'}) == STR("a")); + assert(format(STR("{:#}"), Integral{0}) == STR("0")); + assert(format(STR("{:#d}"), Integral{0}) == STR("0")); + assert(format(STR("{:#c}"), Integral{'a'}) == STR("a")); - assert(format(STR("{:#b}"), integral{0}) == STR("0b0")); - assert(format(STR("{:#B}"), integral{0}) == STR("0B0")); + assert(format(STR("{:#b}"), Integral{0}) == STR("0b0")); + assert(format(STR("{:#B}"), Integral{0}) == STR("0B0")); - assert(format(STR("{:#o}"), integral{0}) == STR("0")); - assert(format(STR("{:#o}"), integral{1}) == STR("01")); + assert(format(STR("{:#o}"), Integral{0}) == STR("0")); + assert(format(STR("{:#o}"), Integral{1}) == STR("01")); - assert(format(STR("{:#x}"), integral{0}) == STR("0x0")); - assert(format(STR("{:#X}"), integral{0}) == STR("0X0")); - assert(format(STR("{:#x}"), integral{255}) == STR("0xff")); - assert(format(STR("{:#X}"), integral{255}) == STR("0XFF")); + assert(format(STR("{:#x}"), Integral{0}) == STR("0x0")); + assert(format(STR("{:#X}"), Integral{0}) == STR("0X0")); + assert(format(STR("{:#x}"), Integral{255}) == STR("0xff")); + assert(format(STR("{:#X}"), Integral{255}) == STR("0XFF")); - assert(format(STR("{:+#6x}"), integral{255}) == STR(" +0xff")); + assert(format(STR("{:+#6x}"), Integral{255}) == STR(" +0xff")); - if constexpr (is_signed_v) { - assert(format(STR("{:#o}"), integral{-1}) == STR("-01")); - assert(format(STR("{:#x}"), integral{-255}) == STR("-0xff")); - assert(format(STR("{:#X}"), integral{-255}) == STR("-0XFF")); + if constexpr (is_signed_v) { + assert(format(STR("{:#o}"), Integral{-1}) == STR("-01")); + assert(format(STR("{:#x}"), Integral{-255}) == STR("-0xff")); + assert(format(STR("{:#X}"), Integral{-255}) == STR("-0XFF")); } - if constexpr (is_same_v) { + if constexpr (is_same_v) { assert(format(STR("{:b}"), numeric_limits::min()) == STR("-1000000000000000000000000000000000000000000000000000000000000000")); } // Leading zero - assert(format(STR("{:0}"), integral{0}) == STR("0")); - assert(format(STR("{:03}"), integral{0}) == STR("000")); - assert(format(STR("{:+03}"), integral{0}) == STR("+00")); - assert(format(STR("{:<03}"), integral{0}) == STR("0 ")); - assert(format(STR("{:>03}"), integral{0}) == STR(" 0")); - assert(format(STR("{:+#06X}"), integral{5}) == STR("+0X005")); + assert(format(STR("{:0}"), Integral{0}) == STR("0")); + assert(format(STR("{:03}"), Integral{0}) == STR("000")); + assert(format(STR("{:+03}"), Integral{0}) == STR("+00")); + assert(format(STR("{:<03}"), Integral{0}) == STR("0 ")); + assert(format(STR("{:>03}"), Integral{0}) == STR(" 0")); + assert(format(STR("{:+#06X}"), Integral{5}) == STR("+0X005")); // Width - assert(format(STR("{:3}"), integral{0}) == STR(" 0")); + assert(format(STR("{:3}"), Integral{0}) == STR(" 0")); // Precision - throw_helper(STR("{:.1}"), integral{0}); + throw_helper(STR("{:.1}"), Integral{0}); // Locale #if !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING - assert(format(locale{"en-US"}, STR("{:L}"), integral{0}) == STR("0")); - assert(format(locale{"en-US"}, STR("{:L}"), integral{100}) == STR("100")); - assert(format(locale{"en-US"}, STR("{:L}"), integral{1'000}) == STR("1,000")); - assert(format(locale{"en-US"}, STR("{:L}"), integral{10'000}) == STR("10,000")); - assert(format(locale{"en-US"}, STR("{:L}"), integral{100'000}) == STR("100,000")); - assert(format(locale{"en-US"}, STR("{:L}"), integral{1'000'000}) == STR("1,000,000")); - assert(format(locale{"en-US"}, STR("{:L}"), integral{10'000'000}) == STR("10,000,000")); - assert(format(locale{"en-US"}, STR("{:L}"), integral{100'000'000}) == STR("100,000,000")); - - assert(format(locale{"en-US"}, STR("{:Lx}"), integral{0x123'abc}) == STR("123,abc")); - assert(format(locale{"en-US"}, STR("{:6L}"), integral{1'000}) == STR(" 1,000")); - - assert(format(locale{"hi-IN"}, STR("{:L}"), integral{10'000'000}) == STR("1,00,00,000")); - assert(format(locale{"hi-IN"}, STR("{:L}"), integral{100'000'000}) == STR("10,00,00,000")); - - assert(format(locale{"hi-IN"}, STR("{:Lx}"), integral{0x123'abc}) == STR("1,23,abc")); + assert(format(locale{"en-US"}, STR("{:L}"), Integral{0}) == STR("0")); + assert(format(locale{"en-US"}, STR("{:L}"), Integral{100}) == STR("100")); + assert(format(locale{"en-US"}, STR("{:L}"), Integral{1'000}) == STR("1,000")); + assert(format(locale{"en-US"}, STR("{:L}"), Integral{10'000}) == STR("10,000")); + assert(format(locale{"en-US"}, STR("{:L}"), Integral{100'000}) == STR("100,000")); + assert(format(locale{"en-US"}, STR("{:L}"), Integral{1'000'000}) == STR("1,000,000")); + assert(format(locale{"en-US"}, STR("{:L}"), Integral{10'000'000}) == STR("10,000,000")); + assert(format(locale{"en-US"}, STR("{:L}"), Integral{100'000'000}) == STR("100,000,000")); + + assert(format(locale{"en-US"}, STR("{:Lx}"), Integral{0x123'abc}) == STR("123,abc")); + assert(format(locale{"en-US"}, STR("{:6L}"), Integral{1'000}) == STR(" 1,000")); + + assert(format(locale{"hi-IN"}, STR("{:L}"), Integral{10'000'000}) == STR("1,00,00,000")); + assert(format(locale{"hi-IN"}, STR("{:L}"), Integral{100'000'000}) == STR("10,00,00,000")); + + assert(format(locale{"hi-IN"}, STR("{:Lx}"), Integral{0x123'abc}) == STR("1,23,abc")); #endif // !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING // Type - assert(format(STR("{:b}"), integral{0}) == STR("0")); - assert(format(STR("{:b}"), integral{100}) == STR("1100100")); + assert(format(STR("{:b}"), Integral{0}) == STR("0")); + assert(format(STR("{:b}"), Integral{100}) == STR("1100100")); - assert(format(STR("{:d}"), integral{100}) == STR("100")); + assert(format(STR("{:d}"), Integral{100}) == STR("100")); - throw_helper(STR("{:c}"), integral{numeric_limits::max()} + 1); - if constexpr (is_signed_v) { - throw_helper(STR("{:c}"), integral{numeric_limits::min()} - 1); + throw_helper(STR("{:c}"), Integral{numeric_limits::max()} + 1); + if constexpr (is_signed_v) { + throw_helper(STR("{:c}"), Integral{numeric_limits::min()} - 1); } } From bec21d4008f8957aa3cb56531440a0f06ee74406 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 00:13:10 -0700 Subject: [PATCH 70/94] Change _Align and _Sign's underlying type to uint8_t. _Align is stored as a data member, so this matters. --- stl/inc/format | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index d2bfb67208d..27b0dbebc89 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -69,9 +69,9 @@ class format_error : public runtime_error { using runtime_error::runtime_error; }; -enum class _Align { _None, _Left, _Right, _Center }; +enum class _Align : uint8_t { _None, _Left, _Right, _Center }; -enum class _Sign { _None, _Plus, _Minus, _Space }; +enum class _Sign : uint8_t { _None, _Plus, _Minus, _Space }; enum class _Basic_format_arg_type : uint8_t { _None, From 2e95f19647da10d1223f968057d44bcf11000db8 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 00:23:55 -0700 Subject: [PATCH 71/94] 4-bit bitfield requires strictly less than 16. --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 27b0dbebc89..bde588b6dda 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -89,7 +89,7 @@ enum class _Basic_format_arg_type : uint8_t { _String_type, _Custom_type, }; -static_assert(static_cast(_Basic_format_arg_type::_Custom_type) <= 16); +static_assert(static_cast(_Basic_format_arg_type::_Custom_type) < 16, "must fit in 4-bit bitfield"); _NODISCARD constexpr bool _Is_integral_fmt_type(_Basic_format_arg_type _Ty) { return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_ULong_long_type; From 6aad0473df29d6fbb2977f94d786bf04c4cd984e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 00:27:02 -0700 Subject: [PATCH 72/94] Style: Add newlines. --- stl/inc/format | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/stl/inc/format b/stl/inc/format index bde588b6dda..985cdb4f9cb 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -97,6 +97,7 @@ _NODISCARD constexpr bool _Is_integral_fmt_type(_Basic_format_arg_type _Ty) { _NODISCARD constexpr bool _Is_arithmetic_fmt_type(_Basic_format_arg_type _Ty) { return _Ty > _Basic_format_arg_type::_None && _Ty <= _Basic_format_arg_type::_Long_double_type; } + struct _Auto_id_tag {}; // clang-format off @@ -194,6 +195,7 @@ public: _NODISCARD constexpr const _CharT* _Unchecked_end() const noexcept { return _Format_string._Unchecked_end(); } + constexpr void advance_to(const const_iterator _It) { _Adl_verify_range(_It, _Format_string.end()); // _It must be after _Format_string.begin(). @@ -291,6 +293,7 @@ public: : _Active_state(_Basic_format_arg_type::_String_type), _String_state(_Val) {} explicit basic_format_arg(const handle _Val) noexcept : _Active_state(_Basic_format_arg_type::_Custom_type), _Custom_state(_Val) {} + explicit operator bool() const noexcept { return _Active_state != _Basic_format_arg_type::_None; } @@ -1062,6 +1065,7 @@ public: _Parse_ctx.check_arg_id(_Arg_id); _Dynamic_specs._Dynamic_precision_index = _Verify_dynamic_arg_index_in_range(_Arg_id); } + constexpr void _On_dynamic_precision(const _Auto_id_tag) { _Dynamic_specs._Dynamic_precision_index = _Verify_dynamic_arg_index_in_range(_Parse_ctx.next_arg_id()); } @@ -1872,6 +1876,7 @@ _NODISCARD _OutputIt _Write_integral( if (_Write_leading_zeroes && _Width < _Specs._Width) { _Out = _RANGES fill_n(_STD move(_Out), _Specs._Width - _Width, '0'); } + if (_Separators > 0) { return _Write_separated_integer(_Buffer_start, _End, _Groups, _STD use_facet>(_Locale).thousands_sep(), _Separators, _STD move(_Out)); @@ -2074,6 +2079,7 @@ _NODISCARD _OutputIt _Fmt_write( ++_Width; _Append_decimal = true; } + if (_Specs._Type == 'g' || _Specs._Type == 'G') { auto _Digits = static_cast(_Exponent_start - _Buffer_start); From 34a457250db1cf82e9e34314612d95f8edb40579 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 00:41:26 -0700 Subject: [PATCH 73/94] Bugfix: Add missing typedefs. --- stl/inc/format | 3 +++ 1 file changed, 3 insertions(+) diff --git a/stl/inc/format b/stl/inc/format index 985cdb4f9cb..380041fdaad 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -238,6 +238,9 @@ private: ptrdiff_t _Next_arg_id = 0; }; +using format_parse_context = basic_format_parse_context; +using wformat_parse_context = basic_format_parse_context; + template class basic_format_arg { public: From 87755dcf973ce04479045535b342a60cef9f373f Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 00:48:23 -0700 Subject: [PATCH 74/94] basic_format_arg::handle should store a __cdecl function pointer. --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 380041fdaad..79f85d913b5 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -249,7 +249,7 @@ public: class handle { private: const void* _Ptr; - void (*_Format)(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx, const void*); + void(__cdecl* _Format)(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx, const void*); friend basic_format_arg; public: From a0f0c02acb657ffaae1af6a2b4fe7230814c3df8 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 00:53:35 -0700 Subject: [PATCH 75/94] basic_format_arg::handle::format is const in the Standard. --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 79f85d913b5..c73a3cc1e42 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -262,7 +262,7 @@ public: _Format_ctx.advance_to(_Formatter.format(*static_cast(_Ptr), _Format_ctx)); }) {} - void format(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx) { + void format(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx) const { _Format(_Parse_ctx, _Format_ctx, _Ptr); } }; From e9e7e433f984d7f1551b7ac54880fd3eec4e8ffd Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 01:02:01 -0700 Subject: [PATCH 76/94] Bugfix: visit_format_arg() must return decltype(auto). --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index c73a3cc1e42..a2f76899160 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -321,7 +321,7 @@ public: }; template -auto visit_format_arg(_Visitor&& _Vis, basic_format_arg<_Context> _Arg) { +decltype(auto) visit_format_arg(_Visitor&& _Vis, basic_format_arg<_Context> _Arg) { switch (_Arg._Active_state) { case _Basic_format_arg_type::_None: return _STD forward<_Visitor>(_Vis)(_Arg._No_state); From 2cd72e6955d3bba7bfd3985a59e00f06af4003b7 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 01:23:11 -0700 Subject: [PATCH 77/94] Style: constexpr explicit is conventional. --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index a2f76899160..364f52002fa 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -895,7 +895,7 @@ struct _Dynamic_format_specs : _Basic_format_specs<_CharT> { template class _Specs_setter { public: - explicit constexpr _Specs_setter(_Basic_format_specs<_CharT>& _Specs_) : _Specs(_Specs_) {} + constexpr explicit _Specs_setter(_Basic_format_specs<_CharT>& _Specs_) : _Specs(_Specs_) {} constexpr void _On_align(const _Align _Aln) { _Specs._Alignment = _Aln; From 46e8971a52813e1ef495c39c856297e03c4eb8e4 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 01:33:50 -0700 Subject: [PATCH 78/94] Add _NODISCARD to _Find_encoded(). --- stl/inc/format | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 364f52002fa..70b8c926811 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -801,7 +801,8 @@ _NODISCARD constexpr const _CharT* _Parse_replacement_field( } template -const _CharT* _Find_encoded(const _CharT* _First, const _CharT* _Last, const _CharT _Val, const _Cvtvec& _Cvt) { +_NODISCARD 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 constexpr (_Is_execution_charset_utf8_v) { From 0f4dd38e1de13f9dabef6cfd0e2add6effd24ef6 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 01:45:01 -0700 Subject: [PATCH 79/94] Style: Use braces to construct temporaries. --- stl/inc/format | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 70b8c926811..415cb16712d 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1004,7 +1004,7 @@ public: template _NODISCARD constexpr int _Get_dynamic_specs(const _FormatArg _Arg) { _STL_INTERNAL_STATIC_ASSERT(_Is_any_of_v<_Handler, _Width_checker, _Precision_checker>); - const unsigned long long _Val = _STD visit_format_arg(_Handler(), _Arg); + const unsigned long long _Val = _STD visit_format_arg(_Handler{}, _Arg); if (_Val > static_cast((numeric_limits::max)())) { _THROW(format_error("Number is too big.")); } @@ -2446,7 +2446,7 @@ struct _Format_handler { _Basic_format_specs<_CharT> _Specs; _Specs_checker<_Specs_handler, _Context>> _Handler( - _Specs_handler, _Context>(_Specs, _Parse_context, _Ctx), + _Specs_handler, _Context>{_Specs, _Parse_context, _Ctx}, _Arg._Active_state); _Begin = _Parse_format_specs(_Begin, _End, _Handler); if (_Begin == _End || *_Begin != '}') { @@ -2473,7 +2473,7 @@ struct _Formatter_base { using _Pc = basic_format_parse_context<_CharT>; typename _Pc::iterator parse(_Pc& _ParseCtx) { - _Specs_checker<_Dynamic_specs_handler<_Pc>> _Handler(_Dynamic_specs_handler<_Pc>(_Specs, _ParseCtx), _ArgType); + _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("Missing '}' in format string.")); From f2b2a6cf3b4414dc324e2823b0882a03d98885df Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 01:55:37 -0700 Subject: [PATCH 80/94] Style: Avoid shadowing, remove unnecessary static_cast. --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 415cb16712d..0a0e6a05abe 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1268,7 +1268,7 @@ struct _Format_arg_store_packed_index { using _Index_type = size_t; constexpr _Format_arg_store_packed_index() = default; - constexpr explicit _Format_arg_store_packed_index(const size_t _Index) : _Index(static_cast<_Index_type>(_Index)) { + constexpr explicit _Format_arg_store_packed_index(const size_t _Index_) : _Index(_Index_) { _Type(_Basic_format_arg_type::_None); } From 95958fedf760e46a170943177edca0a59d9428c1 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 02:16:20 -0700 Subject: [PATCH 81/94] alignas(_Index_type) _Format_arg_store::_Storage, fix comment. --- stl/inc/format | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 0a0e6a05abe..6a8754ab5f3 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1299,8 +1299,8 @@ private: static constexpr size_t _Index_length = _Num_args * sizeof(_Index_type); static constexpr size_t _Storage_length = (_Get_format_arg_storage_size<_Context, _Args> + ... + 0); - // we store the data in memory as _Format_arg_store_packed_index[_Index_length] + unsigned char[_Storage_length] - unsigned char _Storage[_Index_length + _Storage_length]; + // we store the data in memory as _Format_arg_store_packed_index[_Num_args] + unsigned char[_Storage_length] + alignas(_Index_type) unsigned char _Storage[_Index_length + _Storage_length]; template void _Store_impl(const size_t _Arg_index, const _Basic_format_arg_type _Arg_type, _Ty _Val) noexcept { From 8246a766648d1f6cbbe0657309bcf728076712e6 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 12 Apr 2021 20:42:10 -0700 Subject: [PATCH 82/94] Improve comments. --- stl/inc/format | 36 +++++++++++-------- .../P0645R10_text_formatting_parsing/test.cpp | 2 +- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 6a8754ab5f3..205ce6853e3 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -839,7 +839,7 @@ void _Parse_format_string(basic_string_view<_CharT> _Format_str, _HandlerT&& _Ha for (;;) { const _CharT* _ClosingCurl = _Find_encoded(_Begin, _OpeningCurl, _CharT{'}'}, _Cvt); - // In this case there are neither closing nor opening curls in [_Begin, _OpenCurl) + // In this case there are neither closing nor opening curls in [_Begin, _OpeningCurl) // Write the whole thing out. if (_ClosingCurl == _OpeningCurl) { _Handler._On_text(_Begin, _OpeningCurl); @@ -884,8 +884,8 @@ struct _Basic_format_specs { }; // Adds width and precision references to _Basic_format_specs. -// this is required for std::formatter implementations because we must -// parse the format specs without having access to the format args (via a format context) +// This is required for std::formatter implementations because we must +// parse the format specs without having access to the format args (via a format context). template struct _Dynamic_format_specs : _Basic_format_specs<_CharT> { int _Dynamic_width_index = -1; @@ -1012,8 +1012,8 @@ _NODISCARD constexpr int _Get_dynamic_specs(const _FormatArg _Arg) { return static_cast(_Val); } -// Parses standard format specs into a _Basic_format_specs using _Specs_setter, and, -// in addition handles dynamic width and precision. This is separate from _Specs setter +// Parses standard format specs into a _Basic_format_specs using _Specs_setter, and +// additionally handles dynamic width and precision. This is separate from _Specs_setter // because it needs to know about the current basic_format_parse_context and basic_format_context // in order to fetch the width from the arguments. template @@ -1316,7 +1316,7 @@ private: } } - // See [format.arg]/5 + // See N4885 [format.arg]/5 // clang-format off template requires _Has_formatter<_Context, _Ty> @@ -1502,10 +1502,16 @@ _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, monostate) { return _Out; } -// This size is derived from the maximum length of an arithmetic type. The two contenders for widest are double and long -// long. long long has a max length of ceil(log_10(2^64)) = 20 characters. double has a max length of -// limits::max_digits10 + the decimal + the sign + e + the exponent's sign + ceil(log_10(DBL_MAX_10_EXP)) -// = 17 + 1 + 1 + 1 + 3 = 24. An example is DBL_MAX which is "-1.7976931348623158e+308". +// This size is derived from the maximum length of an arithmetic type. The contenders for widest are: +// (a) long long has a max length of 20 characters: LLONG_MIN is "-9223372036854775807". +// (b) unsigned long long has a max length of 20 characters: ULLONG_MAX is "18446744073709551615". +// (c) double has a max length of 24 characters: -DBL_MAX is "-1.7976931348623158e+308". +// That's 17 characters for numeric_limits::max_digits10, +// plus 1 character for the sign, +// plus 1 character for the decimal point, +// plus 1 character for 'e', +// plus 1 character for the exponent's sign, +// plus 3 characters for the max exponent. inline constexpr size_t _Format_min_buffer_length = 24; // clang-format off @@ -2191,7 +2197,7 @@ _NODISCARD _OutputIt _Fmt_write( // 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 hex number represents (log2(16) = log2(2^4) = 4). + // Divide that by the amount of bits a hexit represents (log2(16) = log2(2^4) = 4). // Add 2 for the 0x prefix. auto _Width = 2 + static_cast(_STD bit_width(reinterpret_cast(_Value)) + 3) / 4; @@ -2369,10 +2375,10 @@ _NODISCARD _OutputIt _Fmt_write( }); } -// This is the visitor that's used for "simple" replacement fields, -// it could be a generic lambda (with overloaded), but that's -// bad for throughput. A simple replacement field is a replacement field -// that's just "{}", without any format specs. +// This is the visitor that's used for "simple" replacement fields. +// It could be a generic lambda, but that's bad for throughput. +// A simple replacement field is a replacement field that's just "{}", +// without any format specs. template struct _Default_arg_formatter { using _Context = basic_format_context<_OutputIt, _CharT>; diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index 8b90053be26..d82b6985af4 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -31,7 +31,7 @@ bool test_parse_align() { {.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 '>', + // 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 // \x343E (which is from CJK unified ideographs extension A) and similar characters to parse as // an alignment specifier. From 110bc90f9d73c77a213592b76bd7e0059a659bac Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 03:01:47 -0700 Subject: [PATCH 83/94] Style: Adjust spacing. --- stl/inc/format | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 205ce6853e3..c8051849377 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1516,7 +1516,7 @@ inline constexpr size_t _Format_min_buffer_length = 24; // clang-format off template - requires(is_arithmetic_v<_Arithmetic> && !_CharT_or_bool<_Arithmetic, _CharT>) + requires (is_arithmetic_v<_Arithmetic> && !_CharT_or_bool<_Arithmetic, _CharT>) _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _Arithmetic _Value); // clang-format on @@ -1765,7 +1765,7 @@ _NODISCARD _OutputIt _Write_integral( // clang-format off template - requires(!_CharT_or_bool<_Integral, _CharT>) + requires (!_CharT_or_bool<_Integral, _CharT>) _NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const _Integral _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale); // clang-format on From d1fad5437035a0e1930d8bffec933187fa932b39 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 03:04:21 -0700 Subject: [PATCH 84/94] Style: When declaring functions, don't mark value params as const. --- stl/inc/format | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index c8051849377..446bbfeb519 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1517,23 +1517,23 @@ inline constexpr size_t _Format_min_buffer_length = 24; // clang-format off template requires (is_arithmetic_v<_Arithmetic> && !_CharT_or_bool<_Arithmetic, _CharT>) -_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _Arithmetic _Value); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, _Arithmetic _Value); // clang-format on template -_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const bool _Value); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, bool _Value); template -_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _CharT _Value); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, _CharT _Value); template -_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const void* const _Value); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const void* _Value); template _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const _CharT* _Value); template -_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const basic_string_view<_CharT> _Value); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, basic_string_view<_CharT> _Value); #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch @@ -1761,29 +1761,27 @@ _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, monostate, const _Basic_format_s template _NODISCARD _OutputIt _Write_integral( - _OutputIt _Out, const _Integral _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); + _OutputIt _Out, _Integral _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); // clang-format off template requires (!_CharT_or_bool<_Integral, _CharT>) _NODISCARD _OutputIt _Fmt_write( - _OutputIt _Out, const _Integral _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale); + _OutputIt _Out, _Integral _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale); // clang-format on template -_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const bool _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, bool _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); template -_NODISCARD _OutputIt _Fmt_write( - _OutputIt _Out, const _CharT _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, _CharT _Value, _Basic_format_specs<_CharT> _Specs, locale _Locale); template _NODISCARD _OutputIt _Fmt_write( - _OutputIt _Out, const _Float _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale); + _OutputIt _Out, _Float _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale); template -_NODISCARD _OutputIt _Fmt_write( - _OutputIt _Out, const void* const _Value, const _Basic_format_specs<_CharT>& _Specs, locale); +_NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const void* _Value, const _Basic_format_specs<_CharT>& _Specs, locale); template _NODISCARD _OutputIt _Fmt_write( @@ -1791,7 +1789,7 @@ _NODISCARD _OutputIt _Fmt_write( template _NODISCARD _OutputIt _Fmt_write( - _OutputIt _Out, const basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs, locale); + _OutputIt _Out, basic_string_view<_CharT> _Value, const _Basic_format_specs<_CharT>& _Specs, locale); #pragma warning(push) #pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch From d2b6ebbce00cbee3ad3fc7a6acbc2440fc018e8e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 03:28:17 -0700 Subject: [PATCH 85/94] Use isfinite for simplicity. --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 446bbfeb519..97e1da6b997 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -2061,7 +2061,7 @@ _NODISCARD _OutputIt _Fmt_write( _Exponent = static_cast(_CSTD toupper(_Exponent)); } - const auto _Is_finite = !(_STD isnan)(_Value) && !(_STD isinf)(_Value); + const auto _Is_finite = (_STD isfinite)(_Value); auto _Append_decimal = false; auto _Exponent_start = _Result.ptr; From 0acf9ecf448fd1dee3389ad34b52513f695c8126 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 03:47:22 -0700 Subject: [PATCH 86/94] Style: Use _Nx for N (it's not a type, or a number of types). --- stl/inc/format | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 97e1da6b997..bcee8dea899 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -2545,9 +2545,9 @@ template <_Format_supported_charT _CharT> struct formatter : _Formatter_base {}; -template <_Format_supported_charT _CharT, size_t _Nty> -struct formatter - : _Formatter_base {}; +template <_Format_supported_charT _CharT, size_t _Nx> +struct formatter + : _Formatter_base {}; template <_Format_supported_charT _CharT, class _Traits, class _Allocator> struct formatter, _CharT> From 7bbec5b329aa8c1d4d974ac94415ac6c9a2b903b Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 04:13:08 -0700 Subject: [PATCH 87/94] Big rename: _Align to _Fmt_align. --- stl/inc/chrono | 6 +- stl/inc/format | 60 +++++++++---------- tests/std/include/test_format_support.hpp | 10 ++-- .../test.cpp | 8 +-- .../test.cpp | 14 ++--- .../test.cpp | 6 +- .../P0645R10_text_formatting_parsing/test.cpp | 28 ++++----- .../P0645R10_text_formatting_utf8/test.cpp | 6 +- 8 files changed, 69 insertions(+), 69 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 0fc32ee7efe..94027aadf50 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5187,7 +5187,7 @@ namespace chrono { && _Parse_precision_callbacks<_Ty, _CharT> && _Width_adapter_callbacks<_Ty, _CharT> && _Precision_adapter_callbacks<_Ty, _CharT> - && requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln) { + && requires(_Ty _At, basic_string_view<_CharT> _Sv, _Fmt_align _Aln) { { _At._On_conversion_spec(_CharT{}, _CharT{}) } -> same_as; { _At._On_lit_char(_CharT{}) } -> same_as; }; @@ -5206,7 +5206,7 @@ namespace chrono { int _Precision = -1; int _Dynamic_width_index = -1; int _Dynamic_precision_index = -1; - _Align _Alignment = _Align::_None; + _Fmt_align _Alignment = _Fmt_align::_None; uint8_t _Fill_length = 1; // At most one codepoint (so one char32_t or four utf-8 char8_t) _CharT _Fill[4 / sizeof(_CharT)] = {_CharT{' '}}; @@ -5221,7 +5221,7 @@ namespace chrono { constexpr explicit _Chrono_specs_setter(_Chrono_format_specs<_CharT>& _Specs_) : _Specs(_Specs_) {} // same as _Specs_setter - constexpr void _On_align(_Align _Aln) { + constexpr void _On_align(_Fmt_align _Aln) { _Specs._Alignment = _Aln; } diff --git a/stl/inc/format b/stl/inc/format index bcee8dea899..a41bf068fa9 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -69,7 +69,7 @@ class format_error : public runtime_error { using runtime_error::runtime_error; }; -enum class _Align : uint8_t { _None, _Left, _Right, _Center }; +enum class _Fmt_align : uint8_t { _None, _Left, _Right, _Center }; enum class _Sign : uint8_t { _None, _Plus, _Minus, _Space }; @@ -116,7 +116,7 @@ concept _Parse_replacement_field_callbacks = requires(_Ty _At, const _CharT* _Be }; template -concept _Parse_align_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln) { +concept _Parse_align_callbacks = requires(_Ty _At, basic_string_view<_CharT> _Sv, _Fmt_align _Aln) { { _At._On_fill(_Sv) } -> same_as; { _At._On_align(_Aln) } -> same_as; }; @@ -149,7 +149,7 @@ concept _Parse_spec_callbacks = _Parse_align_callbacks<_Ty, _CharT> && _Parse_precision_callbacks<_Ty, _CharT> && _Width_adapter_callbacks<_Ty, _CharT> && _Precision_adapter_callbacks<_Ty, _CharT> - && requires(_Ty _At, basic_string_view<_CharT> _Sv, _Align _Aln, _Sign _Sgn) { + && requires(_Ty _At, basic_string_view<_CharT> _Sv, _Fmt_align _Aln, _Sign _Sgn) { { _At._On_sign(_Sgn) } -> same_as; { _At._On_hash() } -> same_as; { _At._On_zero() } -> same_as; @@ -539,7 +539,7 @@ template _Callbacks_type> _NODISCARD const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { // align and fill _STL_INTERNAL_CHECK(_Begin != _End && *_Begin != '}'); - auto _Parsed_align = _Align::_None; + auto _Parsed_align = _Fmt_align::_None; const int _Units = _Code_units_in_next_character(_Begin, _End, _Getcvt()); if (_Units < 0) { // invalid fill character encoding @@ -554,17 +554,17 @@ _NODISCARD const _CharT* _Parse_align(const _CharT* _Begin, const _CharT* _End, for (;;) { switch (*_Align_pt) { case '<': - _Parsed_align = _Align::_Left; + _Parsed_align = _Fmt_align::_Left; break; case '>': - _Parsed_align = _Align::_Right; + _Parsed_align = _Fmt_align::_Right; break; case '^': - _Parsed_align = _Align::_Center; + _Parsed_align = _Fmt_align::_Center; break; } - if (_Parsed_align != _Align::_None) { + if (_Parsed_align != _Fmt_align::_None) { if (_Align_pt != _Begin) { if (*_Begin == '{') { _THROW(format_error("invalid fill character '{'")); @@ -870,15 +870,15 @@ void _Parse_format_string(basic_string_view<_CharT> _Format_str, _HandlerT&& _Ha template struct _Basic_format_specs { - int _Width = 0; - int _Precision = -1; - char _Type = '\0'; - _Align _Alignment = _Align::_None; - _Sign _Sgn = _Sign::_None; - bool _Alt = false; - bool _Localized = false; - bool _Leading_zero = false; - uint8_t _Fill_length = 1; + int _Width = 0; + int _Precision = -1; + char _Type = '\0'; + _Fmt_align _Alignment = _Fmt_align::_None; + _Sign _Sgn = _Sign::_None; + bool _Alt = false; + bool _Localized = false; + bool _Leading_zero = false; + uint8_t _Fill_length = 1; // At most one codepoint (so one char32_t or four utf-8 char8_t). _CharT _Fill[4 / sizeof(_CharT)] = {_CharT{' '}}; }; @@ -898,7 +898,7 @@ class _Specs_setter { public: constexpr explicit _Specs_setter(_Basic_format_specs<_CharT>& _Specs_) : _Specs(_Specs_) {} - constexpr void _On_align(const _Align _Aln) { + constexpr void _On_align(const _Fmt_align _Aln) { _Specs._Alignment = _Aln; } @@ -1600,28 +1600,28 @@ _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const basic_string_view<_CharT> template _NODISCARD _OutputIt _Write_aligned(_OutputIt _Out, const int _Width, const _Basic_format_specs<_CharT>& _Specs, - const _Align _Default_align, _Func&& _Fn) { + const _Fmt_align _Default_align, _Func&& _Fn) { int _Fill_left = 0; int _Fill_right = 0; auto _Alignment = _Specs._Alignment; - if (_Alignment == _Align::_None) { + if (_Alignment == _Fmt_align::_None) { _Alignment = _Default_align; } if (_Width < _Specs._Width) { switch (_Alignment) { - case _Align::_Left: + case _Fmt_align::_Left: _Fill_right = _Specs._Width - _Width; break; - case _Align::_Right: + case _Fmt_align::_Right: _Fill_left = _Specs._Width - _Width; break; - case _Align::_Center: + case _Fmt_align::_Center: _Fill_left = (_Specs._Width - _Width) / 2; _Fill_right = _Specs._Width - _Width - _Fill_left; break; - case _Align::_None: + case _Fmt_align::_None: _STL_ASSERT(false, "Invalid alignment"); break; } @@ -1874,7 +1874,7 @@ _NODISCARD _OutputIt _Write_integral( _Width += _Separators; } - const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Align::_None; + const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Fmt_align::_None; auto _Writer = [&, _End = _End](_OutputIt _Out) { #pragma warning(push) #pragma warning(disable : 4296) // '<': expression is always false @@ -1896,7 +1896,7 @@ _NODISCARD _OutputIt _Write_integral( return _Writer(_STD move(_Out)); } - return _Write_aligned(_STD move(_Out), _Width, _Specs, _Align::_Right, _Writer); + return _Write_aligned(_STD move(_Out), _Width, _Specs, _Fmt_align::_Right, _Writer); } #pragma warning(pop) @@ -2122,7 +2122,7 @@ _NODISCARD _OutputIt _Fmt_write( _Width += _Zeroes_to_append; - const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Align::_None && _Is_finite; + const bool _Write_leading_zeroes = _Specs._Leading_zero && _Specs._Alignment == _Fmt_align::_None && _Is_finite; auto _Writer = [&](_OutputIt _Out) { _Out = _Write_sign(_STD move(_Out), _Sgn, _Is_negative); @@ -2162,7 +2162,7 @@ _NODISCARD _OutputIt _Fmt_write( return _Writer(_STD move(_Out)); } - return _Write_aligned(_STD move(_Out), _Width, _Specs, _Align::_Right, _Writer); + return _Write_aligned(_STD move(_Out), _Width, _Specs, _Fmt_align::_Right, _Writer); } #pragma warning(pop) @@ -2204,7 +2204,7 @@ _NODISCARD _OutputIt _Fmt_write( _Width = 3; } - return _Write_aligned(_STD move(_Out), _Width, _Specs, _Align::_Left, + return _Write_aligned(_STD move(_Out), _Width, _Specs, _Fmt_align::_Left, [=](_OutputIt _Out) { return _Fmt_write<_CharT>(_STD move(_Out), _Value); }); } @@ -2368,7 +2368,7 @@ _NODISCARD _OutputIt _Fmt_write( 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 _Write_aligned(_STD move(_Out), _Width, _Specs, _Fmt_align::_Left, [=](_OutputIt _Out) { return _Fmt_write(_STD move(_Out), basic_string_view<_CharT>{_Value.data(), _Last}); }); } diff --git a/tests/std/include/test_format_support.hpp b/tests/std/include/test_format_support.hpp index d3bf9b92ac1..fa4246738ca 100644 --- a/tests/std/include/test_format_support.hpp +++ b/tests/std/include/test_format_support.hpp @@ -31,7 +31,7 @@ struct choose_literal { template struct noop_testing_callbacks { - constexpr void _On_align(std::_Align) {} + constexpr void _On_align(std::_Fmt_align) {} constexpr void _On_fill(std::basic_string_view) {} constexpr void _On_width(unsigned int) {} constexpr void _On_dynamic_width(std::size_t) {} @@ -48,8 +48,8 @@ struct noop_testing_callbacks { template struct testing_callbacks { - std::_Align expected_alignment = std::_Align::_None; - std::_Sign expected_sign = std::_Sign::_None; + std::_Fmt_align expected_alignment = std::_Fmt_align::_None; + std::_Sign expected_sign = std::_Sign::_None; std::basic_string_view expected_fill; int expected_width = -1; std::size_t expected_dynamic_width = static_cast(-1); @@ -62,7 +62,7 @@ struct testing_callbacks { bool expected_localized = false; CharT expected_type = '\0'; - constexpr void _On_align(std::_Align aln) { + constexpr void _On_align(std::_Fmt_align aln) { assert(aln == expected_alignment); } constexpr void _On_fill(std::basic_string_view str_view) { @@ -103,7 +103,7 @@ struct testing_callbacks { } }; template -testing_callbacks(std::_Align, std::basic_string_view) -> testing_callbacks; +testing_callbacks(std::_Fmt_align, std::basic_string_view) -> testing_callbacks; struct testing_arg_id_callbacks { constexpr void _On_auto_id() {} 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 4aaf3288dd4..2ae3a52bb3e 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 @@ -36,7 +36,7 @@ struct choose_literal { template struct testing_callbacks { - _Align expected_alignment = _Align::_None; + _Fmt_align expected_alignment = _Fmt_align::_None; basic_string_view expected_fill; int expected_width = -1; size_t expected_dynamic_width = static_cast(-1); @@ -47,7 +47,7 @@ struct testing_callbacks { vector<_Chrono_specs>& expected_chrono_specs; size_t curr_index = 0; - void _On_align(_Align aln) { + void _On_align(_Fmt_align aln) { assert(aln == expected_alignment); } void _On_fill(basic_string_view str_view) { @@ -168,14 +168,14 @@ bool test_parse_chrono_format_specs() { vector v4{{._Lit_char = 'h'}, {._Lit_char = 'i'}}; test_parse_helper(parse_chrono_format_specs_fn, s4, false, s4.size(), - {.expected_alignment = _Align::_Left, + {.expected_alignment = _Fmt_align::_Left, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), .expected_width = 6, .expected_chrono_specs = v4}); vector v5{{._Type = 'y'}, {._Lit_char = 'm'}, {._Lit_char = 'm'}}; test_parse_helper(parse_chrono_format_specs_fn, s5, false, s5.size(), - {.expected_alignment = _Align::_Center, + {.expected_alignment = _Fmt_align::_Center, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), .expected_width = 4, .expected_precision = 4, diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 5f3b28a6a8d..bccd47e9893 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -445,7 +445,7 @@ void test_fill_and_align() { auto tester = [&] { basic_string output_string; - (void) _Write_aligned(back_inserter(output_string), 2, specs, _Align::_Left, writer); + (void) _Write_aligned(back_inserter(output_string), 2, specs, _Fmt_align::_Left, writer); return output_string; }; @@ -456,24 +456,24 @@ void test_fill_and_align() { specs._Width = 5; - specs._Alignment = _Align::_Left; + specs._Alignment = _Fmt_align::_Left; assert(tester() == STR("AB ")); - specs._Alignment = _Align::_Right; + specs._Alignment = _Fmt_align::_Right; assert(tester() == STR(" AB")); - specs._Alignment = _Align::_Center; + specs._Alignment = _Fmt_align::_Center; assert(tester() == STR(" AB ")); - specs._Alignment = _Align::_Left; + specs._Alignment = _Fmt_align::_Left; specs._Fill[0] = {'*'}; assert(tester() == STR("AB***")); - specs._Alignment = _Align::_Right; + specs._Alignment = _Fmt_align::_Right; assert(tester() == STR("***AB")); - specs._Alignment = _Align::_Center; + specs._Alignment = _Fmt_align::_Center; assert(tester() == STR("*AB**")); } diff --git a/tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/test.cpp b/tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/test.cpp index af57f610fc3..beb4a499c62 100644 --- a/tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_legacy_text_encoding/test.cpp @@ -49,11 +49,11 @@ void test_parse_align() { { assert(setlocale(LC_ALL, ".932") != nullptr); test_parse_helper(parse_align_fn, "\x93\xfaX"sv, false, 3, - {.expected_alignment = _Align::_Right, .expected_fill = "\x96\x7b"sv}); + {.expected_alignment = _Fmt_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}); + {.expected_alignment = _Fmt_align::_Center, .expected_fill = "\x92\x6e"sv}); } assert(setlocale(LC_ALL, "C") != nullptr); diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index d82b6985af4..e44b7792888 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -24,11 +24,11 @@ bool test_parse_align() { view_typ s3(TYPED_LITERAL(CharT, "*^")); test_parse_helper(parse_align_fn, s1, false, view_typ::npos, - {.expected_alignment = _Align::_Left, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*"))}); + {.expected_alignment = _Fmt_align::_Left, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*"))}); test_parse_helper(parse_align_fn, s2, false, view_typ::npos, - {.expected_alignment = _Align::_Right, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*"))}); + {.expected_alignment = _Fmt_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, "*"))}); + {.expected_alignment = _Fmt_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 '>', @@ -41,11 +41,11 @@ bool test_parse_align() { // 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}); + {.expected_alignment = _Fmt_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}); + {.expected_alignment = _Fmt_align::_Center, .expected_fill = L"\U0001F3C8"sv}); } } else { // test multibyte fill characters @@ -54,11 +54,11 @@ bool test_parse_align() { assert(setlocale(LC_ALL, ".UTF-8") != nullptr); // "\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}); + {.expected_alignment = _Fmt_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}); + {.expected_alignment = _Fmt_align::_Center, .expected_fill = "\xf0\x9f\x8f\x88"sv}); } #endif // MSVC_INTERNAL_TESTING @@ -167,27 +167,27 @@ bool test_parse_format_specs() { view_typ s6(TYPED_LITERAL(CharT, "*^+#04.4La}")); test_parse_helper(parse_format_specs_fn, s0, false, s0.size() - 1, {.expected_width = 6}); test_parse_helper(parse_format_specs_fn, s1, false, s1.size(), - {.expected_alignment = _Align::_Left, + {.expected_alignment = _Fmt_align::_Left, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), .expected_width = 6}); test_parse_helper(parse_format_specs_fn, s2, false, s2.size() - 1, - {.expected_alignment = _Align::_Right, + {.expected_alignment = _Fmt_align::_Right, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), .expected_width = 6}); test_parse_helper(parse_format_specs_fn, s3, false, s3.size() - 1, - {.expected_alignment = _Align::_Center, + {.expected_alignment = _Fmt_align::_Center, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), .expected_width = 6}); test_parse_helper(parse_format_specs_fn, s4, false, s4.size() - 1, {.expected_width = 6, .expected_type = 'd'}); test_parse_helper(parse_format_specs_fn, s5, false, s5.size() - 1, - {.expected_alignment = _Align::_Center, + {.expected_alignment = _Fmt_align::_Center, .expected_sign = _Sign::_Plus, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), .expected_width = 4, .expected_precision = 4, .expected_type = 'a'}); test_parse_helper(parse_format_specs_fn, s6, false, s6.size() - 1, - {.expected_alignment = _Align::_Center, + {.expected_alignment = _Fmt_align::_Center, .expected_sign = _Sign::_Plus, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), .expected_width = 4, diff --git a/tests/std/tests/P0645R10_text_formatting_utf8/test.cpp b/tests/std/tests/P0645R10_text_formatting_utf8/test.cpp index 4e6c895c5b5..5b988794238 100644 --- a/tests/std/tests/P0645R10_text_formatting_utf8/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_utf8/test.cpp @@ -35,11 +35,11 @@ void test_parse_align() { { // "\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}); + {.expected_alignment = _Fmt_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}); + {.expected_alignment = _Fmt_align::_Center, .expected_fill = "\xf0\x9f\x8f\x88"sv}); } } From a04936db6afbc63dd86d5b0c1bb20f1c738971b3 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Apr 2021 04:15:11 -0700 Subject: [PATCH 88/94] Big rename: _Sign to _Fmt_sign. --- stl/inc/format | 40 +++++++++---------- tests/std/include/test_format_support.hpp | 6 +-- .../P0645R10_text_formatting_parsing/test.cpp | 4 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index a41bf068fa9..1970efdedbd 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -71,7 +71,7 @@ class format_error : public runtime_error { enum class _Fmt_align : uint8_t { _None, _Left, _Right, _Center }; -enum class _Sign : uint8_t { _None, _Plus, _Minus, _Space }; +enum class _Fmt_sign : uint8_t { _None, _Plus, _Minus, _Space }; enum class _Basic_format_arg_type : uint8_t { _None, @@ -149,7 +149,7 @@ concept _Parse_spec_callbacks = _Parse_align_callbacks<_Ty, _CharT> && _Parse_precision_callbacks<_Ty, _CharT> && _Width_adapter_callbacks<_Ty, _CharT> && _Precision_adapter_callbacks<_Ty, _CharT> - && requires(_Ty _At, basic_string_view<_CharT> _Sv, _Fmt_align _Aln, _Sign _Sgn) { + && requires(_Ty _At, basic_string_view<_CharT> _Sv, _Fmt_align _Aln, _Fmt_sign _Sgn) { { _At._On_sign(_Sgn) } -> same_as; { _At._On_hash() } -> same_as; { _At._On_zero() } -> same_as; @@ -702,15 +702,15 @@ _NODISCARD constexpr const _CharT* _Parse_format_specs( switch (*_Begin) { case '+': - _Callbacks._On_sign(_Sign::_Plus); + _Callbacks._On_sign(_Fmt_sign::_Plus); ++_Begin; break; case '-': - _Callbacks._On_sign(_Sign::_Minus); + _Callbacks._On_sign(_Fmt_sign::_Minus); ++_Begin; break; case ' ': - _Callbacks._On_sign(_Sign::_Space); + _Callbacks._On_sign(_Fmt_sign::_Space); ++_Begin; break; default: @@ -874,7 +874,7 @@ struct _Basic_format_specs { int _Precision = -1; char _Type = '\0'; _Fmt_align _Alignment = _Fmt_align::_None; - _Sign _Sgn = _Sign::_None; + _Fmt_sign _Sgn = _Fmt_sign::_None; bool _Alt = false; bool _Localized = false; bool _Leading_zero = false; @@ -912,7 +912,7 @@ public: _Specs._Fill_length = static_cast(_Sv.size()); } - constexpr void _On_sign(const _Sign _Sgn) { + constexpr void _On_sign(const _Fmt_sign _Sgn) { _Specs._Sgn = _Sgn; } @@ -1663,19 +1663,19 @@ _NODISCARD constexpr string_view _Get_integral_prefix(const char _Type, const _I } template -_NODISCARD _OutputIt _Write_sign(_OutputIt _Out, const _Sign _Sgn, const bool _Is_negative) { +_NODISCARD _OutputIt _Write_sign(_OutputIt _Out, const _Fmt_sign _Sgn, const bool _Is_negative) { if (_Is_negative) { *_Out++ = '-'; } else { switch (_Sgn) { - case _Sign::_Plus: + case _Fmt_sign::_Plus: *_Out++ = '+'; break; - case _Sign::_Space: + case _Fmt_sign::_Space: *_Out++ = ' '; break; - case _Sign::_None: - case _Sign::_Minus: + case _Fmt_sign::_None: + case _Fmt_sign::_Minus: break; } } @@ -1808,8 +1808,8 @@ _NODISCARD _OutputIt _Write_integral( _THROW(format_error("integral cannot have a precision")); } - if (_Specs._Sgn == _Sign::_None) { - _Specs._Sgn = _Sign::_Minus; + if (_Specs._Sgn == _Fmt_sign::_None) { + _Specs._Sgn = _Fmt_sign::_Minus; } int _Base = 10; @@ -1847,7 +1847,7 @@ _NODISCARD _OutputIt _Write_integral( auto _Width = static_cast(_End - _Buffer_start); if (_Value >= _Integral{0}) { - if (_Specs._Sgn != _Sign::_Minus) { + if (_Specs._Sgn != _Fmt_sign::_Minus) { _Width += 1; } } else { @@ -1956,8 +1956,8 @@ template _NODISCARD _OutputIt _Fmt_write( _OutputIt _Out, const _Float _Value, const _Basic_format_specs<_CharT>& _Specs, locale _Locale) { auto _Sgn = _Specs._Sgn; - if (_Sgn == _Sign::_None) { - _Sgn = _Sign::_Minus; + if (_Sgn == _Fmt_sign::_None) { + _Sgn = _Fmt_sign::_Minus; } auto _To_upper = false; @@ -2051,7 +2051,7 @@ _NODISCARD _OutputIt _Fmt_write( // Remove the '-', it will be dealt with directly _Buffer_start += 1; } else { - if (_Sgn != _Sign::_Minus) { + if (_Sgn != _Fmt_sign::_Minus) { _Width += 1; } } @@ -2173,7 +2173,7 @@ _NODISCARD _OutputIt _Fmt_write( _THROW(format_error("invalid const void* type")); } - if (_Specs._Sgn != _Sign::_None) { + if (_Specs._Sgn != _Fmt_sign::_None) { _THROW(format_error("const void* cannot have a sign")); } @@ -2345,7 +2345,7 @@ _NODISCARD _OutputIt _Fmt_write( _THROW(format_error("invalid string type")); } - if (_Specs._Sgn != _Sign::_None) { + if (_Specs._Sgn != _Fmt_sign::_None) { _THROW(format_error("string cannot have a sign")); } diff --git a/tests/std/include/test_format_support.hpp b/tests/std/include/test_format_support.hpp index fa4246738ca..89dd541e85c 100644 --- a/tests/std/include/test_format_support.hpp +++ b/tests/std/include/test_format_support.hpp @@ -39,7 +39,7 @@ struct noop_testing_callbacks { constexpr void _On_precision(unsigned int) {} constexpr void _On_dynamic_precision(std::size_t) {} constexpr void _On_dynamic_precision(std::_Auto_id_tag) {} - constexpr void _On_sign(std::_Sign) {} + constexpr void _On_sign(std::_Fmt_sign) {} constexpr void _On_hash() {} constexpr void _On_zero() {} constexpr void _On_localized() {} @@ -49,7 +49,7 @@ struct noop_testing_callbacks { template struct testing_callbacks { std::_Fmt_align expected_alignment = std::_Fmt_align::_None; - std::_Sign expected_sign = std::_Sign::_None; + std::_Fmt_sign expected_sign = std::_Fmt_sign::_None; std::basic_string_view expected_fill; int expected_width = -1; std::size_t expected_dynamic_width = static_cast(-1); @@ -86,7 +86,7 @@ struct testing_callbacks { constexpr void _On_dynamic_precision(std::_Auto_id_tag) { assert(expected_auto_dynamic_precision); } - constexpr void _On_sign(std::_Sign sgn) { + constexpr void _On_sign(std::_Fmt_sign sgn) { assert(sgn == expected_sign); } constexpr void _On_hash() { diff --git a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp index e44b7792888..ddf204c0db5 100644 --- a/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_parsing/test.cpp @@ -181,14 +181,14 @@ bool test_parse_format_specs() { test_parse_helper(parse_format_specs_fn, s4, false, s4.size() - 1, {.expected_width = 6, .expected_type = 'd'}); test_parse_helper(parse_format_specs_fn, s5, false, s5.size() - 1, {.expected_alignment = _Fmt_align::_Center, - .expected_sign = _Sign::_Plus, + .expected_sign = _Fmt_sign::_Plus, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), .expected_width = 4, .expected_precision = 4, .expected_type = 'a'}); test_parse_helper(parse_format_specs_fn, s6, false, s6.size() - 1, {.expected_alignment = _Fmt_align::_Center, - .expected_sign = _Sign::_Plus, + .expected_sign = _Fmt_sign::_Plus, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), .expected_width = 4, .expected_precision = 4, From baf1fbc79f119b8fc1bc408d2bf7cfbaf538d4ac Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 13 Apr 2021 15:32:39 -0700 Subject: [PATCH 89/94] move monostate. --- stl/inc/format | 1 - stl/inc/variant | 5 +---- stl/inc/xutility | 5 +++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 1970efdedbd..1530e1cd916 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -53,7 +53,6 @@ #include #include #include -#include #include #pragma pack(push, _CRT_PACKING) diff --git a/stl/inc/variant b/stl/inc/variant index 036e1d602aa..85edcc36668 100644 --- a/stl/inc/variant +++ b/stl/inc/variant @@ -1187,7 +1187,7 @@ public: { constexpr size_t _That_idx = decltype(_That_ref)::_Idx; #ifdef __EDG__ // TRANSITION, VSO-657455 - constexpr size_t _My_idx = decltype(_My_ref)::_Idx + 0 * _That_idx; + constexpr size_t _My_idx = decltype(_My_ref)::_Idx + 0 * _That_idx; #else // ^^^ workaround ^^^ / vvv no workaround vvv constexpr size_t _My_idx = decltype(_My_ref)::_Idx; #endif // TRANSITION, VSO-657455 @@ -1731,9 +1731,6 @@ constexpr _Ret visit(_Callable&& _Obj, _Variants&&... _Args) { } #endif // _HAS_CXX20 -// CLASS monostate [variant.monostate] -struct monostate {}; - // monostate RELATIONAL OPERATORS [variant.monostate.relops] _NODISCARD constexpr bool operator==(monostate, monostate) noexcept { return true; diff --git a/stl/inc/xutility b/stl/inc/xutility index 6d0c570ed3d..5a0fa8bef80 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -5961,6 +5961,11 @@ struct _Nontrivial_dummy_type { }; _STL_INTERNAL_STATIC_ASSERT(!is_trivially_default_constructible_v<_Nontrivial_dummy_type>); +#ifdef __cpp_lib_variant +// CLASS monostate [variant.monostate] +struct monostate {}; +#endif // __cpp_lib_variant + _STD_END #pragma pop_macro("new") _STL_RESTORE_CLANG_WARNINGS From c3a056c32d07710eb66f879d5aba8be312c69a73 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 13 Apr 2021 15:57:04 -0700 Subject: [PATCH 90/94] fix bad clang-format formatting. --- stl/inc/variant | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/variant b/stl/inc/variant index 85edcc36668..a6359e0e6b7 100644 --- a/stl/inc/variant +++ b/stl/inc/variant @@ -1187,7 +1187,7 @@ public: { constexpr size_t _That_idx = decltype(_That_ref)::_Idx; #ifdef __EDG__ // TRANSITION, VSO-657455 - constexpr size_t _My_idx = decltype(_My_ref)::_Idx + 0 * _That_idx; + constexpr size_t _My_idx = decltype(_My_ref)::_Idx + 0 * _That_idx; #else // ^^^ workaround ^^^ / vvv no workaround vvv constexpr size_t _My_idx = decltype(_My_ref)::_Idx; #endif // TRANSITION, VSO-657455 From 14df470114acd1846613f895acdaa9cd8ec66c94 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 14 Apr 2021 15:48:34 -0700 Subject: [PATCH 91/94] add tests for runtime width from libfmt + bugfix from encoding PR. --- stl/inc/format | 5 +- .../P0645R10_text_formatting_args/test.cpp | 4 - .../test.cpp | 95 +++++++++++++++++-- 3 files changed, 89 insertions(+), 15 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 1530e1cd916..5f2d04d788d 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -680,6 +680,7 @@ _NODISCARD constexpr const _CharT* _Parse_precision( if (_Begin == _End || *_Begin != '}') { _THROW(format_error("Invalid format string.")); } + ++_Begin; } else { _THROW(format_error("Missing precision specifier.")); } @@ -2434,14 +2435,14 @@ struct _Format_handler { } void _On_replacement_field(const size_t _Id, const _CharT*) { - auto _Arg = _Ctx.arg(_Id); + auto _Arg = _Get_arg(_Ctx, _Id); _Ctx.advance_to(_STD visit_format_arg( _Default_arg_formatter<_OutputIt, _CharT>{_Ctx.out(), _Ctx._Get_args(), _Ctx.locale()}, _Arg)); } const _CharT* _On_format_specs(const size_t _Id, const _CharT* _Begin, const _CharT* _End) { _Parse_context.advance_to(_Parse_context.begin() + (_Begin - &*_Parse_context.begin())); - auto _Arg = _Ctx.arg(_Id); + auto _Arg = _Get_arg(_Ctx, _Id); if (_Arg._Active_state == _Basic_format_arg_type::_Custom_type) { _Arg._Custom_state.format(_Parse_context, _Ctx); return _Parse_context.begin()._Unwrapped(); diff --git a/tests/std/tests/P0645R10_text_formatting_args/test.cpp b/tests/std/tests/P0645R10_text_formatting_args/test.cpp index 7264f450826..c78a683a496 100644 --- a/tests/std/tests/P0645R10_text_formatting_args/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_args/test.cpp @@ -118,10 +118,6 @@ void test_basic_format_arg() { basic_format_arg from_string_view{get_input_sv()}; assert(from_string_view); - - // TRANSITION, implement handle - // basic_format_arg from_handle{}; - // assert(from_handle); } } template diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index bccd47e9893..4cebe1393e4 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -1028,6 +1029,18 @@ void libfmt_formatter_test_args_in_different_position() { assert(format(STR("{1} is the {0}"), STR("answer"), 42) == STR("42 is the answer")); assert(format(STR("{0}{1}{0}"), STR("abra"), STR("cad")) == STR("abracadabra")); } + +template +void libfmt_formatter_test_auto_arg_index() { + assert(format(STR("{}{}{}"), 'a', 'b', 'c') == STR("abc")); + throw_helper(STR("{0}{}"), 'a', 'b'); + throw_helper(STR("{}{0}"), 'a', 'b'); + // assert(format(STR("{:.{}}"), 1.2345, 2) == STR("1.2")); + throw_helper(STR("{0}:.{}"), 1.2345, 2); + throw_helper(STR("{:.{0}}"), 1.2345, 2); + throw_helper(STR("{}")); +} + template void libfmt_formatter_test_left_align() { assert(format(STR("{0:<4}"), 42) == STR("42 ")); @@ -1221,6 +1234,69 @@ void libfmt_formatter_test_zero_flag() { throw_helper(STR("{0:05}"), reinterpret_cast(0x42)); } +template +void libfmt_formatter_test_runtime_width() { + throw_helper(STR("{0:{"), 0); + throw_helper(STR("{0:{}"), 0); + throw_helper(STR("{0:{?}}"), 0); + throw_helper(STR("{0:{1}}"), 0); + throw_helper(STR("{0:{0:}}"), 0); + throw_helper(STR("{0:{1}}"), 0, -1); + throw_helper(STR("{0:{1}}"), 0, (INT_MAX + 1u)); + throw_helper(STR("{0:{1}}"), 0, -1l); + throw_helper(STR("{0:{1}}"), 0, (INT_MAX + 1ul)); + assert(format(STR("{0:{1}}"), 0, '0') + == STR(" 0")); // behavior differs from libfmt, but conforms + throw_helper(STR("{0:{1}}"), 0, 0.0); + + assert(format(STR("{0:{1}}"), -42, 4) == STR(" -42")); + assert(format(STR("{0:{1}}"), 42u, 5) == STR(" 42")); + assert(format(STR("{0:{1}}"), -42l, 6) == STR(" -42")); + assert(format(STR("{0:{1}}"), 42ul, 7) == STR(" 42")); + assert(format(STR("{0:{1}}"), -42ll, 6) == STR(" -42")); + assert(format(STR("{0:{1}}"), 42ull, 7) == STR(" 42")); + assert(format(STR("{0:{1}}"), -1.23, 8) == STR(" -1.23")); + assert(format(STR("{0:{1}}"), -1.23l, 9) == STR(" -1.23")); + assert(format(STR("{0:{1}}"), reinterpret_cast(0xcafe), 10) + == STR("0xcafe ")); // behavior differs from libfmt, but conforms + assert(format(STR("{0:{1}}"), 'x', 11) == STR("x ")); + assert(format(STR("{0:{1}}"), STR("str"), 12) == STR("str ")); +} + +template +void libfmt_formatter_test_runtime_precision() { + throw_helper(STR("{0:.{"), 0); + throw_helper(STR("{0:.{}"), 0); + throw_helper(STR("{0:.{?}}"), 0); + throw_helper(STR("{0:.{1}"), 0, 0); + throw_helper(STR("{0:.{1}}"), 0); + throw_helper(STR("{0:.{0:}}"), 0); + throw_helper(STR("{0:.{1}}"), 0, -1); + throw_helper(STR("{0:.{1}}"), 0, (INT_MAX + 1u)); + throw_helper(STR("{0:.{1}}"), 0, -1l); + throw_helper(STR("{0:.{1}}"), 0, (INT_MAX + 1ul)); + throw_helper(STR("{0:.{1}}"), 0, '0'); + throw_helper(STR("{0:.{1}}"), 0, 0.0); + throw_helper(STR("{0:.{1}}"), 42, 2); + throw_helper(STR("{0:.{1}f}"), 42, 2); + throw_helper(STR("{0:.{1}}"), 42u, 2); + throw_helper(STR("{0:.{1}f}"), 42u, 2); + throw_helper(STR("{0:.{1}}"), 42l, 2); + throw_helper(STR("{0:.{1}f}"), 42l, 2); + throw_helper(STR("{0:.{1}}"), 42ul, 2); + throw_helper(STR("{0:.{1}f}"), 42ul, 2); + throw_helper(STR("{0:.{1}}"), 42ll, 2); + throw_helper(STR("{0:.{1}f}"), 42ll, 2); + throw_helper(STR("{0:.{1}}"), 42ull, 2); + throw_helper(STR("{0:.{1}f}"), 42ull, 2); + throw_helper(STR("{0:3.{1}}"), 'x', 0); + assert(format(STR("{0:.{1}}"), 1.2345, 2) == STR("1.2")); + assert(format(STR("{1:.{0}}"), 2, 1.2345l) == STR("1.2")); + throw_helper(STR("{0:.{1}}"), reinterpret_cast(0xcafe), 2); + throw_helper(STR("{0:.{1}f}"), reinterpret_cast(0xcafe), 2); + assert(format(STR("{0:.{1}}"), STR("str"), 2) == STR("st")); +} + void test() { test_simple_formatting(); test_simple_formatting(); @@ -1251,6 +1327,9 @@ void test() { libfmt_formatter_test_args_in_different_position(); libfmt_formatter_test_args_in_different_position(); + libfmt_formatter_test_auto_arg_index(); + libfmt_formatter_test_auto_arg_index(); + libfmt_formatter_test_left_align(); libfmt_formatter_test_left_align(); @@ -1277,16 +1356,14 @@ void test() { libfmt_formatter_test_zero_flag(); libfmt_formatter_test_zero_flag(); + + libfmt_formatter_test_runtime_width(); + libfmt_formatter_test_runtime_width(); + + libfmt_formatter_test_runtime_precision(); + libfmt_formatter_test_runtime_precision(); } int main() { - try { - test(); - } catch (const format_error& e) { - printf("format_error: %s\n", e.what()); - assert(false); - } catch (const exception& e) { - printf("exception: %s\n", e.what()); - assert(false); - } + test(); } From 9612cced17ba5c623e09c0583fcac942bb6ec49c Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 14 Apr 2021 16:00:40 -0700 Subject: [PATCH 92/94] verify range in advance_to --- stl/inc/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/format b/stl/inc/format index 5f2d04d788d..ca12816206a 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -197,7 +197,7 @@ public: constexpr void advance_to(const const_iterator _It) { _Adl_verify_range(_It, _Format_string.end()); - // _It must be after _Format_string.begin(). + _Adl_verify_range(_Format_string.begin() + 1, _It); const auto _Diff = static_cast(_It._Unwrapped() - _Format_string._Unchecked_begin()); _Format_string.remove_prefix(_Diff); } From e8496c66867dfec7c85b48fcec3daf45b09ea5a0 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 14 Apr 2021 16:44:41 -0700 Subject: [PATCH 93/94] _Adl_verify_range the correct range --- stl/inc/format | 2 +- tests/std/tests/P0645R10_text_formatting_formatting/test.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index ca12816206a..939581aff94 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -197,7 +197,7 @@ public: constexpr void advance_to(const const_iterator _It) { _Adl_verify_range(_It, _Format_string.end()); - _Adl_verify_range(_Format_string.begin() + 1, _It); + _Adl_verify_range(_Format_string.begin(), _It); const auto _Diff = static_cast(_It._Unwrapped() - _Format_string._Unchecked_begin()); _Format_string.remove_prefix(_Diff); } diff --git a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp index 4cebe1393e4..ab37ef41f5c 100644 --- a/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp +++ b/tests/std/tests/P0645R10_text_formatting_formatting/test.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -1035,7 +1034,7 @@ void libfmt_formatter_test_auto_arg_index() { assert(format(STR("{}{}{}"), 'a', 'b', 'c') == STR("abc")); throw_helper(STR("{0}{}"), 'a', 'b'); throw_helper(STR("{}{0}"), 'a', 'b'); - // assert(format(STR("{:.{}}"), 1.2345, 2) == STR("1.2")); + assert(format(STR("{:.{}}"), 1.2345, 2) == STR("1.2")); throw_helper(STR("{0}:.{}"), 1.2345, 2); throw_helper(STR("{:.{0}}"), 1.2345, 2); throw_helper(STR("{}")); From c572f7e3ae416e73d675fa4d101ffd29ae4411da Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 14 Apr 2021 21:43:27 -0700 Subject: [PATCH 94/94] Fix intrinsic usage for _M_ARM64EC. --- stl/inc/bit | 22 +++++++++++----------- stl/inc/xcharconv_ryu.h | 19 +++++++++++++------ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/stl/inc/bit b/stl/inc/bit index 56e6cdcb37f..d98eb56c550 100644 --- a/stl/inc/bit +++ b/stl/inc/bit @@ -113,7 +113,7 @@ _NODISCARD constexpr int _Popcount_fallback(_Ty _Val) noexcept { return static_cast(_Val & static_cast<_Ty>(_Digits + _Digits - 1)); } -#if defined(_M_IX86) || defined(_M_X64) +#if defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)) extern "C" { extern int __isa_available; @@ -207,7 +207,7 @@ _NODISCARD int _Checked_x86_x64_popcount(const _Ty _Val) noexcept { #endif // _M_IX86 } } -#endif // defined(_M_IX86) || defined(_M_X64) +#endif // defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)) #if defined(_M_ARM) || defined(_M_ARM64) @@ -255,17 +255,17 @@ _NODISCARD int _Checked_arm_arm64_countl_zero(const _Ty _Val) noexcept { template , int> _Enabled> _NODISCARD constexpr int countl_zero(const _Ty _Val) noexcept { - if (_STD is_constant_evaluated()) { - return _Countl_zero_fallback(_Val); - } else { -#if defined(_M_IX86) || defined(_M_X64) +#if defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)) + if (!_STD is_constant_evaluated()) { return _Checked_x86_x64_countl_zero(_Val); + } #elif defined(_M_ARM) || defined(_M_ARM64) + if (!_STD is_constant_evaluated()) { return _Checked_arm_arm64_countl_zero(_Val); -#else -#error Unsupported Hardware -#endif } +#endif // defined(_M_ARM) || defined(_M_ARM64) + + return _Countl_zero_fallback(_Val); } template , int> = 0> @@ -285,11 +285,11 @@ _NODISCARD constexpr int countr_one(const _Ty _Val) noexcept { template , int> _Enabled = 0> _NODISCARD constexpr int popcount(const _Ty _Val) noexcept { -#if defined(_M_IX86) || defined(_M_X64) +#if defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)) if (!_STD is_constant_evaluated()) { return _Checked_x86_x64_popcount(_Val); } -#endif // defined(_M_IX86) || defined(_M_X64) +#endif // defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)) return _Popcount_fallback(_Val); } diff --git a/stl/inc/xcharconv_ryu.h b/stl/inc/xcharconv_ryu.h index e045f8e5a37..282e18dc1bf 100644 --- a/stl/inc/xcharconv_ryu.h +++ b/stl/inc/xcharconv_ryu.h @@ -45,9 +45,15 @@ #include #include -#ifdef _M_X64 +#if defined(_M_X64) && !defined(_M_ARM64EC) +#define _HAS_CHARCONV_INTRINSICS 1 +#else // ^^^ intrinsics available ^^^ / vvv intrinsics unavailable vvv +#define _HAS_CHARCONV_INTRINSICS 0 +#endif // ^^^ intrinsics unavailable ^^^ + +#if _HAS_CHARCONV_INTRINSICS #include // for _umul128() and __shiftright128() -#endif // _M_X64 +#endif // ^^^ intrinsics available ^^^ #if !_HAS_CXX17 #error The contents of are only available with C++17. (Also, you should not include this internal header.) @@ -138,7 +144,7 @@ inline constexpr int __DOUBLE_POW5_BITCOUNT = 121; // vvvvvvvvvv DERIVED FROM d2s_intrinsics.h vvvvvvvvvv -#ifdef _M_X64 +#if _HAS_CHARCONV_INTRINSICS _NODISCARD inline uint64_t __ryu_umul128(const uint64_t __a, const uint64_t __b, uint64_t* const __productHi) { return _umul128(__a, __b, __productHi); @@ -324,7 +330,7 @@ _NODISCARD inline bool __multipleOfPowerOf2(const uint64_t __value, const uint32 inline constexpr int __POW10_ADDITIONAL_BITS = 120; -#ifdef _M_X64 +#if _HAS_CHARCONV_INTRINSICS // Returns the low 64 bits of the high 128 bits of the 256-bit product of a and b. _NODISCARD inline uint64_t __umul256_hi128_lo64( const uint64_t __aHi, const uint64_t __aLo, const uint64_t __bHi, const uint64_t __bLo) { @@ -373,7 +379,7 @@ _NODISCARD inline uint32_t __mulShift_mod1e9(const uint64_t __m, const uint64_t* const uint64_t __s1high = __high2 + __c2; // 192 _STL_INTERNAL_CHECK(__j >= 128); _STL_INTERNAL_CHECK(__j <= 180); -#ifdef _M_X64 +#if _HAS_CHARCONV_INTRINSICS const uint32_t __dist = static_cast(__j - 128); // __dist: [0, 52] const uint64_t __shiftedhigh = __s1high >> __dist; const uint64_t __shiftedlow = __ryu_shiftright128(__s1low, __s1high, __dist); @@ -1655,7 +1661,7 @@ _NODISCARD pair<_CharT*, errc> __f2s_buffered_n(_CharT* const _First, _CharT* co // c. Split only the first factor into 31-bit pieces, which also guarantees // no internal overflow, but requires extra work since the intermediate // results are not perfectly aligned. -#ifdef _M_X64 +#if _HAS_CHARCONV_INTRINSICS _NODISCARD inline uint64_t __mulShift(const uint64_t __m, const uint64_t* const __mul, const int32_t __j) { // __m is maximum 55 bits @@ -2407,6 +2413,7 @@ _NODISCARD to_chars_result _Floating_to_chars_fixed_precision( _STD_END +#undef _HAS_CHARCONV_INTRINSICS #undef _WIDEN #pragma pop_macro("new")