From 411510655e33835b6383ffe0878cb3630aaa8837 Mon Sep 17 00:00:00 2001 From: Charles Milette Date: Thu, 14 Jan 2021 21:03:20 -0500 Subject: [PATCH 01/10] : Remove tzcnt special case for small integers in countr_zero (#1540) Co-authored-by: Stephan T. Lavavej --- stl/inc/limits | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/stl/inc/limits b/stl/inc/limits index f93a63ac90f..bfe6ee02843 100644 --- a/stl/inc/limits +++ b/stl/inc/limits @@ -1058,9 +1058,14 @@ _NODISCARD int _Checked_x86_x64_countr_zero(const _Ty _Val) noexcept { constexpr _Ty _Max = (numeric_limits<_Ty>::max)(); #ifndef __AVX2__ - const bool _Definitely_have_tzcnt = __isa_available >= __ISA_AVAILABLE_AVX2; - if (!_Definitely_have_tzcnt && _Val == 0) { - return _Digits; + // Because the widening done below will always give a non-0 value, checking for tzcnt + // is not required for 8-bit and 16-bit since the only difference in behavior between + // bsf and tzcnt is when the value is 0. + if constexpr (_Digits > 16) { + const bool _Definitely_have_tzcnt = __isa_available >= __ISA_AVAILABLE_AVX2; + if (!_Definitely_have_tzcnt && _Val == 0) { + return _Digits; + } } #endif // __AVX2__ From b6ee7a6e38d23ee14f0f1ef691bac652b36cdcad Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 14 Jan 2021 18:09:50 -0800 Subject: [PATCH 02/10] Fix boolalpha extraction to be case-sensitive again (#1543) --- stl/inc/xlocale | 12 ++++-- stl/inc/xlocnum | 2 +- stl/inc/xloctime | 6 +-- tests/std/test.lst | 1 + .../env.lst | 4 ++ .../test.cpp | 41 +++++++++++++++++++ 6 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 tests/std/tests/GH_001541_case_sensitive_boolalpha/env.lst create mode 100644 tests/std/tests/GH_001541_case_sensitive_boolalpha/test.cpp diff --git a/stl/inc/xlocale b/stl/inc/xlocale index 107e80d0a5a..326a0db4703 100644 --- a/stl/inc/xlocale +++ b/stl/inc/xlocale @@ -3242,8 +3242,11 @@ protected: }; // FUNCTION TEMPLATE _Getloctxt -template -int __CRTDECL _Getloctxt(_InIt& _First, _InIt& _Last, size_t _Numfields, const _Elem* _Ptr) { +enum class _Case_sensitive : bool { _No, _Yes }; + +template +int __CRTDECL _Getloctxt( + _InIt& _First, _InIt& _Last, size_t _Numfields, const _Elem* _Ptr, const _Case_sensitive _Matching) { // find field at _Ptr that matches longest in [_First, _Last) for (size_t _Off = 0; _Ptr[_Off] != _Elem{}; ++_Off) { if (_Ptr[_Off] == _Ptr[0]) { @@ -3271,7 +3274,10 @@ int __CRTDECL _Getloctxt(_InIt& _First, _InIt& _Last, size_t _Numfields, const _ || _Ptr[_Off] == _Elem{}) { // matched all of field, save as possible answer _Str[_Field] = static_cast(_Column < 127 ? _Column : 127); // save skip count if small enough _Ans = static_cast(_Field); // save answer - } else if (_First == _Last || _CType.tolower(_Ptr[_Off]) != _CType.tolower(static_cast<_Elem>(*_First))) { + } else if (_First == _Last + || (_Matching == _Case_sensitive::_Yes + ? _Ptr[_Off] != *_First + : _CType.tolower(_Ptr[_Off]) != _CType.tolower(static_cast<_Elem>(*_First)))) { _Str[_Field] = static_cast(_Column < 127 ? _Column : 127); // no match, just save skip count } else { _Prefix = true; // still a valid prefix diff --git a/stl/inc/xlocnum b/stl/inc/xlocnum index 89d33a7d02f..2643b4cbe56 100644 --- a/stl/inc/xlocnum +++ b/stl/inc/xlocnum @@ -369,7 +369,7 @@ protected: _Str += _Punct_fac.falsename(); _Str.push_back(_Elem{}); _Str += _Punct_fac.truename(); // construct "\0false\0true" - switch (_Getloctxt(_First, _Last, 2, _Str.c_str())) { + switch (_Getloctxt(_First, _Last, 2, _Str.c_str(), _Case_sensitive::_Yes)) { case 0: _Val = false; break; diff --git a/stl/inc/xloctime b/stl/inc/xloctime index 55cb9f0df2d..6edfeb10649 100644 --- a/stl/inc/xloctime +++ b/stl/inc/xloctime @@ -326,7 +326,7 @@ protected: virtual _InIt __CLR_OR_THIS_CALL do_get_weekday(_InIt _First, _InIt _Last, ios_base&, ios_base::iostate& _State, tm* _Pt) const { // get weekday from [_First, _Last) into _Pt - int _Num = _Getloctxt(_First, _Last, 0, _Days); + int _Num = _Getloctxt(_First, _Last, 0, _Days, _Case_sensitive::_No); if (_Num < 0) { _State |= ios_base::failbit; } else { @@ -338,7 +338,7 @@ protected: virtual _InIt __CLR_OR_THIS_CALL do_get_monthname(_InIt _First, _InIt _Last, ios_base&, ios_base::iostate& _State, tm* _Pt) const { // get month from [_First, _Last) into _Pt - int _Num = _Getloctxt(_First, _Last, 0, _Months); + int _Num = _Getloctxt(_First, _Last, 0, _Months, _Case_sensitive::_No); if (_Num < 0) { _State |= ios_base::failbit; @@ -444,7 +444,7 @@ protected: break; case 'p': - _Ans = _Getloctxt(_First, _Last, 0, ":AM:am:PM:pm"); + _Ans = _Getloctxt(_First, _Last, 0, ":AM:am:PM:pm", _Case_sensitive::_No); if (_Ans < 0) { _State |= ios_base::failbit; } else if (1 < _Ans) { diff --git a/tests/std/test.lst b/tests/std/test.lst index 3d71f043206..acdf8d9b478 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -172,6 +172,7 @@ tests\GH_001103_countl_zero_correctness tests\GH_001105_custom_streambuf_throws tests\GH_001123_random_cast_out_of_range tests\GH_001411_core_headers +tests\GH_001541_case_sensitive_boolalpha tests\LWG2597_complex_branch_cut tests\LWG3018_shared_ptr_function tests\P0019R8_atomic_ref diff --git a/tests/std/tests/GH_001541_case_sensitive_boolalpha/env.lst b/tests/std/tests/GH_001541_case_sensitive_boolalpha/env.lst new file mode 100644 index 00000000000..19f025bd0e6 --- /dev/null +++ b/tests/std/tests/GH_001541_case_sensitive_boolalpha/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_matrix.lst diff --git a/tests/std/tests/GH_001541_case_sensitive_boolalpha/test.cpp b/tests/std/tests/GH_001541_case_sensitive_boolalpha/test.cpp new file mode 100644 index 00000000000..42cdbdc11d5 --- /dev/null +++ b/tests/std/tests/GH_001541_case_sensitive_boolalpha/test.cpp @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +using namespace std; + +enum class Parse { Success, Failure }; + +struct TestCase { + const char* str; + ios_base::fmtflags flags; + Parse expected; + bool result; +}; + +// clang-format off +constexpr TestCase test_cases[] = { + {"0", ios_base::fmtflags{}, Parse::Success, false}, + {"1", ios_base::fmtflags{}, Parse::Success, true }, + {"2", ios_base::fmtflags{}, Parse::Failure, true }, // N4868 [facet.num.get.virtuals]/6 + {"WOOF", ios_base::fmtflags{}, Parse::Failure, false}, // N4868 [facet.num.get.virtuals]/3.6 + {"false", ios_base::boolalpha, Parse::Success, false}, + {"true", ios_base::boolalpha, Parse::Success, true }, + {"WOOF", ios_base::boolalpha, Parse::Failure, false}, // N4868 [facet.num.get.virtuals]/7 + {"FALSE", ios_base::boolalpha, Parse::Failure, false}, // GH-1541 + {"TRUE", ios_base::boolalpha, Parse::Failure, false}, // GH-1541 +}; +// clang-format on + +int main() { + for (const auto& test : test_cases) { + bool val = !test.result; + istringstream iss(test.str); + iss.setf(test.flags); + iss >> val; + assert(iss.fail() == (test.expected == Parse::Failure)); + assert(val == test.result); + } +} From 3a7946ebf439d3ffc722f0407976d4fa1ca798b2 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 14 Jan 2021 18:23:16 -0800 Subject: [PATCH 03/10] Add header-units.json (#1545) --- .vscode/settings.json | 1 + stl/CMakeLists.txt | 1 + stl/inc/header-units.json | 149 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 stl/inc/header-units.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 5a97ba29cee..5312b7ed5b4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "editor.formatOnSave": true, "files.associations": { ".clang-format": "yaml", + "header-units.json": "jsonc", "**/stl/inc/**": "cpp" }, "files.eol": "\r\n", diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index 394f28cd064..6f2686e6de2 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -146,6 +146,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/future ${CMAKE_CURRENT_LIST_DIR}/inc/hash_map ${CMAKE_CURRENT_LIST_DIR}/inc/hash_set + ${CMAKE_CURRENT_LIST_DIR}/inc/header-units.json ${CMAKE_CURRENT_LIST_DIR}/inc/initializer_list ${CMAKE_CURRENT_LIST_DIR}/inc/iomanip ${CMAKE_CURRENT_LIST_DIR}/inc/ios diff --git a/stl/inc/header-units.json b/stl/inc/header-units.json new file mode 100644 index 00000000000..cb1ef9efeb5 --- /dev/null +++ b/stl/inc/header-units.json @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +{ + "Version": "1.0", + "BuildAsHeaderUnits": [ + // "__msvc_all_public_headers.hpp", // for testing, not production + "__msvc_system_error_abi.hpp", + "algorithm", + "any", + "array", + "atomic", + "barrier", + "bit", + "bitset", + // "cassert", // design is permanently incompatible with header units + // "ccomplex", // removed in C++20 + "cctype", + "cerrno", + "cfenv", + "cfloat", + "charconv", + "chrono", + "cinttypes", + // "ciso646", // removed in C++20 + "climits", + "clocale", + "cmath", + "codecvt", + "compare", + "complex", + "concepts", + "condition_variable", + "coroutine", + "csetjmp", + "csignal", + // "cstdalign", // removed in C++20 + "cstdarg", + // "cstdbool", // removed in C++20 + "cstddef", + "cstdint", + "cstdio", + "cstdlib", + "cstring", + // "ctgmath", // removed in C++20 + "ctime", + "cuchar", + "cwchar", + "cwctype", + "deque", + "exception", + "execution", + "filesystem", + "forward_list", + "fstream", + "functional", + "future", + // "hash_map", // non-Standard, will be removed soon + // "hash_set", // non-Standard, will be removed soon + "initializer_list", + "iomanip", + "ios", + "iosfwd", + "iostream", + "iso646.h", + "istream", + "iterator", + "latch", + "limits", + "list", + "locale", + "map", + "memory", + "memory_resource", + "mutex", + "new", + "numbers", + "numeric", + "optional", + "ostream", + "queue", + "random", + "ranges", + "ratio", + "regex", + "scoped_allocator", + "semaphore", + "set", + "shared_mutex", + "span", + "sstream", + "stack", + "stdexcept", + "stop_token", + "streambuf", + "string", + "string_view", + "strstream", + "system_error", + "thread", + "tuple", + "type_traits", + "typeindex", + "typeinfo", + "unordered_map", + "unordered_set", + "use_ansi.h", + "utility", + "valarray", + "variant", + "vector", + "version", + "xatomic.h", + "xatomic_wait.h", + "xbit_ops.h", + "xcall_once.h", + "xcharconv.h", + "xcharconv_ryu.h", + "xcharconv_ryu_tables.h", + "xerrc.h", + "xfacet", + "xfilesystem_abi.h", + "xhash", + "xiosbase", + // "xkeycheck.h", // internal header, provides no machinery, scans for macroized keywords only + "xlocale", + "xlocbuf", + "xlocinfo", + "xlocinfo.h", + "xlocmes", + "xlocmon", + "xlocnum", + "xloctime", + "xmemory", + "xnode_handle.h", + "xpolymorphic_allocator.h", + "xsmf_control.h", + "xstddef", + "xstring", + "xthreads.h", + "xtimec.h", + "xtr1common", + "xtree", + "xutility", + "ymath.h", + "yvals.h", + "yvals_core.h" + ] +} From 85ad05396a1e3ef8e7468974a6ae653b84711f2a Mon Sep 17 00:00:00 2001 From: MattStephanson <68978048+MattStephanson@users.noreply.github.com> Date: Thu, 14 Jan 2021 18:28:03 -0800 Subject: [PATCH 04/10] : fill should accept volatile byte pointers (#1557) Co-authored-by: Stephan T. Lavavej --- stl/inc/xutility | 5 +++-- tests/std/tests/VSO_0180469_fill_family/test.cpp | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/stl/inc/xutility b/stl/inc/xutility index 653646d5425..f484b23b1f9 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -4361,11 +4361,12 @@ struct _Is_character_or_byte_or_bool : true_type {}; template <> struct _Is_character_or_byte_or_bool : true_type {}; -// _Fill_memset_is_safe determines if _FwdIt and _Ty are eligible for memset optimization in fill +// _Fill_memset_is_safe determines if _FwdIt and _Ty are eligible for memset optimization in fill. +// Need to explicitly test for volatile because _Unwrap_enum_t discards qualifiers. template > _INLINE_VAR constexpr bool _Fill_memset_is_safe = conjunction_v, _Is_character_or_byte_or_bool<_Unwrap_enum_t>>>, - is_assignable<_Iter_ref_t<_FwdIt>, const _Ty&>>; + negation>>>, is_assignable<_Iter_ref_t<_FwdIt>, const _Ty&>>; template _INLINE_VAR constexpr bool _Fill_memset_is_safe<_FwdIt, _Ty, false> = false; diff --git a/tests/std/tests/VSO_0180469_fill_family/test.cpp b/tests/std/tests/VSO_0180469_fill_family/test.cpp index c932d75f092..6c3bef212b9 100644 --- a/tests/std/tests/VSO_0180469_fill_family/test.cpp +++ b/tests/std/tests/VSO_0180469_fill_family/test.cpp @@ -134,6 +134,9 @@ int main() { test_fill(); test_fill(); // Test GH-1183 +#ifdef __cpp_lib_byte + test_fill(); // Test GH-1556 +#endif // __cpp_lib_byte test_uninitialized_fill( [](count_copies* buff, size_t n, const count_copies& src) { uninitialized_fill(buff, buff + n, src); }); From 40b39d24011a9661b459494ed2e2e85e315f50a1 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Fri, 15 Jan 2021 03:32:51 +0100 Subject: [PATCH 05/10] Fix ranges::unique for already unique input (#1561) Co-authored-by: Stephan T. Lavavej --- stl/inc/algorithm | 1 + .../tests/P0896R4_ranges_alg_unique/test.cpp | 18 ++++ .../P0896R4_ranges_alg_unique_copy/test.cpp | 86 ++++++++++++------- 3 files changed, 75 insertions(+), 30 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 9146dd1077f..8eb7c131ae2 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -4174,6 +4174,7 @@ namespace ranges { for (;; ++_Current) { if (++_First == _Last) { + ++_Current; return {_STD move(_Current), _STD move(_First)}; } diff --git a/tests/std/tests/P0896R4_ranges_alg_unique/test.cpp b/tests/std/tests/P0896R4_ranges_alg_unique/test.cpp index 8e1ab7324c0..b214962d7b6 100644 --- a/tests/std/tests/P0896R4_ranges_alg_unique/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_unique/test.cpp @@ -45,6 +45,15 @@ struct instantiator { assert(comparisonCounter == size(input) - 1); } + { // Validate already unique range returns empty subrange + P input[4] = {{0, 99}, {1, 47}, {3, 99}, {4, 47}}; + ReadWrite wrapped_input{input}; + + auto result = unique(wrapped_input.begin(), wrapped_input.end(), countedEq, get_second); + STATIC_ASSERT(same_as>>); + assert(result.empty()); + } + comparisonCounter = 0; { // Validate range overload @@ -58,6 +67,15 @@ struct instantiator { assert(equal(expected, span{input}.first<4>())); assert(comparisonCounter == size(input) - 1); } + + { // Validate already unique range returns empty subrange + P input[4] = {{0, 99}, {1, 47}, {3, 99}, {4, 47}}; + ReadWrite wrapped_input{input}; + + auto result = unique(wrapped_input, countedEq, get_second); + STATIC_ASSERT(same_as>>); + assert(result.empty()); + } } }; diff --git a/tests/std/tests/P0896R4_ranges_alg_unique_copy/test.cpp b/tests/std/tests/P0896R4_ranges_alg_unique_copy/test.cpp index 71e6a268aee..71407f89798 100644 --- a/tests/std/tests/P0896R4_ranges_alg_unique_copy/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_unique_copy/test.cpp @@ -34,21 +34,34 @@ struct test_iterator_overload { static constexpr void call() { using ranges::unique_copy, ranges::unique_copy_result, ranges::equal, ranges::equal_to, ranges::iterator_t, ranges::size; - // Validate iterator + sentinel overload - P input[6] = {{0, 99}, {0, 47}, {0, 47}, {0, 99}, {0, 47}, {0, 47}}; - P output[4] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; - Read wrapped_input{input}; - - const same_as, Write>> auto result = - unique_copy(wrapped_input.begin(), wrapped_input.end(), Write{output}, equal_to{}, countedProjection); - assert(result.in == wrapped_input.end()); - assert(result.out.peek() == end(output)); - if constexpr (input_iterator || !ranges::forward_range) { - assert(equal(output, expectedOutputRead)); - assert(equal(input, expectedInputRead)); - } else { - assert(equal(output, expectedOutput)); - assert(equal(input, expectedInput)); + { // Validate iterator + sentinel overload + P input[6] = {{0, 99}, {0, 47}, {0, 47}, {0, 99}, {0, 47}, {0, 47}}; + P output[4] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; + Read wrapped_input{input}; + + const same_as, Write>> auto result = + unique_copy(wrapped_input.begin(), wrapped_input.end(), Write{output}, equal_to{}, countedProjection); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == end(output)); + if constexpr (input_iterator || !ranges::forward_range) { + assert(equal(output, expectedOutputRead)); + assert(equal(input, expectedInputRead)); + } else { + assert(equal(output, expectedOutput)); + assert(equal(input, expectedInput)); + } + } + + { // Validate already unique range + P input[4] = {{0, 99}, {0, 47}, {0, 99}, {0, 47}}; + P output[4] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; + Read wrapped_input{input}; + + const same_as, Write>> auto result = + unique_copy(wrapped_input.begin(), wrapped_input.end(), Write{output}, equal_to{}); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == end(output)); + assert(equal(output, input)); } } }; @@ -58,21 +71,34 @@ struct test_range_overload { static constexpr void call() { using ranges::unique_copy, ranges::unique_copy_result, ranges::equal, ranges::equal_to, ranges::iterator_t, ranges::size; - // Validate range overload - P input[6] = {{0, 99}, {0, 47}, {0, 47}, {0, 99}, {0, 47}, {0, 47}}; - P output[4] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; - Read wrapped_input{input}; - - const same_as, Write>> auto result = - unique_copy(wrapped_input, Write{output}, equal_to{}, countedProjection); - assert(result.in == wrapped_input.end()); - assert(result.out.peek() == end(output)); - if constexpr (input_iterator || !ranges::forward_range) { - assert(equal(output, expectedOutputRead)); - assert(equal(input, expectedInputRead)); - } else { - assert(equal(output, expectedOutput)); - assert(equal(input, expectedInput)); + { // Validate range overload + P input[6] = {{0, 99}, {0, 47}, {0, 47}, {0, 99}, {0, 47}, {0, 47}}; + P output[4] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; + Read wrapped_input{input}; + + const same_as, Write>> auto result = + unique_copy(wrapped_input, Write{output}, equal_to{}, countedProjection); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == end(output)); + if constexpr (input_iterator || !ranges::forward_range) { + assert(equal(output, expectedOutputRead)); + assert(equal(input, expectedInputRead)); + } else { + assert(equal(output, expectedOutput)); + assert(equal(input, expectedInput)); + } + } + + { // Validate already unique range + P input[4] = {{0, 99}, {0, 47}, {0, 99}, {0, 47}}; + P output[4] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; + Read wrapped_input{input}; + + const same_as, Write>> auto result = + unique_copy(wrapped_input, Write{output}, equal_to{}); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == end(output)); + assert(equal(output, input)); } } }; From f93cf3a0e3b6f0f356ffdae0cebc6b772b38481f Mon Sep 17 00:00:00 2001 From: Curtis J Bezault Date: Thu, 14 Jan 2021 18:38:48 -0800 Subject: [PATCH 06/10] Test each list which can contain /BE independently (#1565) --- .../custom_format.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 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 9c5c3858e9f..8fe322f324d 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 @@ -1,7 +1,6 @@ # Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -import itertools import os from stl.test.format import STLTestFormat, TestStep @@ -93,9 +92,13 @@ def getBuildSteps(self, test, litConfig, shared): outputDir, outputBase = test.getTempPaths() sourcePath = test.getSourcePath() - compileTestCppWithEdg = '/BE' in itertools.chain(test.flags, test.compileFlags) - if compileTestCppWithEdg: + compileTestCppWithEdg = False + if '/BE' in test.flags: + compileTestCppWithEdg = True test.flags.remove('/BE') + + if '/BE' in test.compileFlags: + compileTestCppWithEdg = True test.compileFlags.remove('/BE') exportHeaderOptions = ['/exportHeader', '/Fo', '/MP'] From 6c9f6da6ef2d75fcacad9c865687813d88d00a66 Mon Sep 17 00:00:00 2001 From: Berrysoft <37586447+Berrysoft@users.noreply.github.com> Date: Sat, 16 Jan 2021 11:36:47 +0800 Subject: [PATCH 07/10] P0408R7 Efficient Access To basic_stringbuf's Buffer (#919) Co-authored-by: Stephan T. Lavavej --- stl/inc/regex | 2 +- stl/inc/sstream | 365 +++++++++++++++++- stl/inc/xstring | 59 +++ stl/inc/xutility | 5 + stl/inc/yvals_core.h | 1 + tests/std/test.lst | 1 + .../env.lst | 4 + .../test.cpp | 302 +++++++++++++++ .../test.compile.pass.cpp | 2 + 9 files changed, 721 insertions(+), 20 deletions(-) create mode 100644 tests/std/tests/P0408R7_efficient_access_to_stringbuf_buffer/env.lst create mode 100644 tests/std/tests/P0408R7_efficient_access_to_stringbuf_buffer/test.cpp diff --git a/stl/inc/regex b/stl/inc/regex index 32ef9439b46..0cd17c2b3a0 100644 --- a/stl/inc/regex +++ b/stl/inc/regex @@ -936,7 +936,7 @@ basic_ostream<_Elem, _Traits>& operator<<(basic_ostream<_Elem, _Traits>& _Ostr, } // FORWARD DECLARATIONS -template >> +template >> class match_results; template diff --git a/stl/inc/sstream b/stl/inc/sstream index e5c6235cf2f..90ce15f8ea0 100644 --- a/stl/inc/sstream +++ b/stl/inc/sstream @@ -29,6 +29,10 @@ public: using _Mystr = basic_string<_Elem, _Traits, _Alloc>; using _Mysize_type = typename _Mystr::size_type; +#if _HAS_CXX20 + using _Mystr_view = basic_string_view<_Elem, _Traits>; +#endif // _HAS_CXX20 + basic_stringbuf() : _Seekhigh(nullptr), _Mystate(_Getstate(ios_base::in | ios_base::out)), _Al() {} explicit basic_stringbuf(ios_base::openmode _Mode) : _Seekhigh(nullptr), _Mystate(_Getstate(_Mode)), _Al() {} @@ -38,6 +42,37 @@ public: _Init(_Str.c_str(), _Str.size(), _Getstate(_Mode)); } +#if _HAS_CXX20 + explicit basic_stringbuf(const _Alloc& _Al_) : basic_stringbuf(ios_base::in | ios_base::out, _Al_) {} + + basic_stringbuf(ios_base::openmode _Mode, const _Alloc& _Al_) + : _Seekhigh(nullptr), _Mystate(_Getstate(_Mode)), _Al(_Al_) {} + + explicit basic_stringbuf(_Mystr&& _Str, ios_base::openmode _Mode = ios_base::in | ios_base::out) + : _Al(_STD move(_Str._Getal())) { + _Init_string_inplace(_STD move(_Str), _Getstate(_Mode)); + } + + template + basic_stringbuf(const basic_string<_Elem, _Traits, _Alloc2>& _Str, ios_base::openmode _Mode, const _Alloc& _Al_) + : _Al(_Al_) { + _Init(_Str.c_str(), _Str.size(), _Getstate(_Mode)); + } + + template + basic_stringbuf(const basic_string<_Elem, _Traits, _Alloc2>& _Str, const _Alloc& _Al_) + : basic_stringbuf(_Str, ios_base::in | ios_base::out, _Al_) {} + + template , int> = 0> + explicit basic_stringbuf( + const basic_string<_Elem, _Traits, _Alloc2>& _Str, ios_base::openmode _Mode = ios_base::in | ios_base::out) + : basic_stringbuf(_Str, _Mode, _Alloc{}) {} + + basic_stringbuf(basic_stringbuf&& _Right, const _Alloc& _Al_) : _Al(_Al_) { + _Assign_rv(_STD move(_Right)); + } +#endif // _HAS_CXX20 + basic_stringbuf(basic_stringbuf&& _Right) { _Assign_rv(_STD move(_Right)); } @@ -54,12 +89,18 @@ public: } } - void swap(basic_stringbuf& _Right) { + void swap(basic_stringbuf& _Right) noexcept /* strengthened */ { if (this != _STD addressof(_Right)) { + if constexpr (!allocator_traits<_Alloc>::propagate_on_container_swap::value) { + _STL_ASSERT( + _Al == _Right._Al, "The allocators of basic_stringbuf should propagate or be equal on swap."); + } + _Mysb::swap(_Right); _STD swap(_Seekhigh, _Right._Seekhigh); _STD swap(_Mystate, _Right._Mystate); - _Swap_adl(_Al, _Right._Al); + // The same as basic_string::swap + _Pocs(_Al, _Right._Al); } } @@ -71,27 +112,53 @@ public: } enum { // constants for bits in stream state - _Allocated = 1, // set if character array storage has been allocated, eback() points to allocated storage - _Constant = 2, // set if character array nonmutable - _Noread = 4, // set if character array cannot be read - _Append = 8, // set if all writes are appends - _Atend = 16 // set if initial writes are appends + _Allocated = 1, // set if character array storage has been allocated, eback() points to allocated storage + _Constant = 2, // set if character array nonmutable + _Noread = 4, // set if character array cannot be read + _Append = 8, // set if all writes are appends + _Atend = 16, // set if initial writes are appends + _From_rvalue = 32 // set if character array is released from an rvalue of basic_string }; using int_type = typename _Traits::int_type; using pos_type = typename _Traits::pos_type; using off_type = typename _Traits::off_type; - _NODISCARD _Mystr str() const { - _Mystr _Result(_Al); - if (!(_Mystate & _Constant) && _Mysb::pptr()) { // writable, make string from write buffer + struct _Buffer_view { + _Elem* _Ptr; + _Mysize_type _Size; + _Mysize_type _Res; + }; + + _NODISCARD _Buffer_view _Get_buffer_view() const noexcept { + _Buffer_view _Result{}; + if ((!(_Mystate & _Constant) || (_Mystate & _From_rvalue)) && _Mysb::pptr()) { + // writable, make string view from write buffer const auto _Base = _Mysb::pbase(); - _Result.assign(_Base, static_cast<_Mysize_type>((_STD max)(_Mysb::pptr(), _Seekhigh) - _Base)); - } else if (!(_Mystate & _Noread) && _Mysb::gptr()) { // readable, make string from read buffer + _Result._Ptr = _Base; + _Result._Size = static_cast<_Mysize_type>((_STD max)(_Mysb::pptr(), _Seekhigh) - _Base); + _Result._Res = static_cast<_Mysize_type>(_Mysb::epptr() - _Base); + } else if (!(_Mystate & _Noread) && _Mysb::gptr()) { + // readable, make string view from read buffer const auto _Base = _Mysb::eback(); - _Result.assign(_Base, static_cast<_Mysize_type>(_Mysb::egptr() - _Base)); + _Result._Ptr = _Base; + _Result._Size = static_cast<_Mysize_type>(_Mysb::egptr() - _Base); + _Result._Res = _Result._Size; } + return _Result; + } +#if _HAS_CXX20 + _NODISCARD _Mystr str() const& +#else + _NODISCARD _Mystr str() const +#endif // _HAS_CXX20 + { + _Mystr _Result(_Al); + const auto _View = _Get_buffer_view(); + if (_View._Ptr) { + _Result.assign(_View._Ptr, _View._Size); + } return _Result; } @@ -100,6 +167,72 @@ public: _Init(_Newstr.c_str(), _Newstr.size(), _Mystate); } +#if _HAS_CXX20 + template <_Allocator _Alloc2> + _NODISCARD basic_string<_Elem, _Traits, _Alloc2> str(const _Alloc2& _Al) const { + return basic_string<_Elem, _Traits, _Alloc2>{view(), _Al}; + } + + // The buffer cannot be moved to a string directly, because + // the buffer may already be full, and the terminating char is not '\0'. + // In that case, copy the string as usual. + _NODISCARD _Mystr str() && { + _Mystr _Result{_String_constructor_rvalue_allocator_tag{}, _STD move(_Al)}; + const auto _View = _Get_buffer_view(); + // _Size cannot be larger than _Res, but it could be equal, + // because basic_stringbuf doesn't allocate for the terminating '\0'. + if (_View._Size == _View._Res) { + // the buffer is full + _Result.assign(_View._Ptr, _View._Size); + } else { + _Traits::assign(_View._Ptr[_View._Size], _Elem()); + if (_Result._Move_assign_from_buffer(_View._Ptr, _View._Size, _View._Res)) { + _Mystate &= ~_Allocated; + } + } + _Tidy(); + return _Result; + } + + _NODISCARD _Mystr_view view() const noexcept { + const auto _View = _Get_buffer_view(); + return _Mystr_view{_View._Ptr, _View._Size}; + } + + template , int> = 0> + void str(const basic_string<_Elem, _Traits, _Alloc2>& _Newstr) { + _Tidy(); + _Init(_Newstr.c_str(), _Newstr.size(), _Mystate); + } + + void _Str(_Mystr&& _Newstr, _Equal_allocators) { + _Tidy(); + _Pocma(_Al, _Newstr._Getal()); + _Init_string_inplace(_STD move(_Newstr), _Mystate); + } + + void _Str(_Mystr&& _Newstr, _Propagate_allocators) { + _Str(_STD move(_Newstr), _Equal_allocators{}); + } + + void _Str(_Mystr&& _Newstr, _No_propagate_allocators) { + if (_Al == _Newstr._Getal()) { + _Str(_STD move(_Newstr), _Equal_allocators{}); + } else { + _Tidy(); + _Init(_Newstr.c_str(), _Newstr.size(), _Mystate); + } + } + + void str(_Mystr&& _Newstr) { + _Str(_STD move(_Newstr), _Choose_pocma<_Alloc>{}); + } + + _NODISCARD allocator_type get_allocator() const noexcept { + return _Al; + } +#endif // _HAS_CXX20 + protected: virtual int_type overflow(int_type _Meta = _Traits::eof()) { // put an element to stream if (_Mystate & _Constant) { @@ -205,7 +338,7 @@ protected: off_type _Off, ios_base::seekdir _Way, ios_base::openmode _Mode = ios_base::in | ios_base::out) { // change position by _Off, according to _Way, _Mode const auto _Gptr_old = _Mysb::gptr(); - const auto _Pptr_old = _Mysb::pptr(); + const auto _Pptr_old = (_Mystate & _Constant) ? nullptr : _Mysb::pptr(); if (_Pptr_old && _Seekhigh < _Pptr_old) { // update high-water pointer _Seekhigh = _Pptr_old; } @@ -267,7 +400,7 @@ protected: // change position to _Pos, according to _Mode const auto _Off = static_cast(_Pos); const auto _Gptr_old = _Mysb::gptr(); - const auto _Pptr_old = _Mysb::pptr(); + const auto _Pptr_old = (_Mystate & _Constant) ? nullptr : _Mysb::pptr(); if (_Pptr_old && _Seekhigh < _Pptr_old) { // update high-water pointer _Seekhigh = _Pptr_old; } @@ -297,6 +430,8 @@ protected: protected: void _Init(const _Elem* _Ptr, _Mysize_type _Count, int _State) { // initialize buffer to [_Ptr, _Ptr + _Count), set state + _State &= ~_From_rvalue; + if (_Count > INT_MAX) { // TRANSITION, VSO-485517 _Xbad_alloc(); } @@ -328,6 +463,34 @@ protected: _Mystate = _State; } +#if _HAS_CXX20 + void _Init_string_inplace(_Mystr&& _Str, int _State) { + _State |= _From_rvalue; + + auto [_Ptr, _Size, _Res] = _Str._Release_to_buffer(_Al); + _Elem* const _Pnew = _Unfancy(_Ptr); + if (_Res != 0 && (_State & (_Noread | _Constant)) != (_Noread | _Constant)) { + // finite buffer that can be read or written, set it up + _Seekhigh = _Pnew + _Size; + auto _End_buffer = _Pnew + _Res; + + _Mysb::setp(_Pnew, (_State & (_Atend | _Append)) ? _Seekhigh : _Pnew, _End_buffer); + + if (_State & _Noread) { // maintain "_Allocated == eback() points to buffer base" invariant + _Mysb::setg(_Pnew, nullptr, _Pnew); + } else { + _Mysb::setg(_Pnew, _Pnew, _Seekhigh); + } + + _State |= _Allocated; + } else { + _Seekhigh = nullptr; + } + + _Mystate = _State; + } +#endif // _HAS_CXX20 + void _Tidy() noexcept { // discard any allocated buffer and clear pointers if (_Mystate & _Allocated) { _Al.deallocate(_Ptr_traits::pointer_to(*_Mysb::eback()), @@ -369,13 +532,15 @@ private: return _State; } + // TRANSITION, ABI, see GH-938 _Elem* _Seekhigh; // the high-water pointer in character array int _Mystate; // the stream state allocator_type _Al; // the allocator object }; template -void swap(basic_stringbuf<_Elem, _Traits, _Alloc>& _Left, basic_stringbuf<_Elem, _Traits, _Alloc>& _Right) { +void swap(basic_stringbuf<_Elem, _Traits, _Alloc>& _Left, basic_stringbuf<_Elem, _Traits, _Alloc>& _Right) noexcept +/* strengthened */ { _Left.swap(_Right); } @@ -388,6 +553,10 @@ public: using _Mysb = basic_stringbuf<_Elem, _Traits, _Alloc>; using _Mystr = basic_string<_Elem, _Traits, _Alloc>; +#if _HAS_CXX20 + using _Mystr_view = basic_string_view<_Elem, _Traits>; +#endif // _HAS_CXX20 + basic_istringstream() : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(ios_base::in) {} explicit basic_istringstream(ios_base::openmode _Mode) @@ -396,6 +565,27 @@ public: explicit basic_istringstream(const _Mystr& _Str, ios_base::openmode _Mode = ios_base::in) : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode | ios_base::in) {} +#if _HAS_CXX20 + basic_istringstream(ios_base::openmode _Mode, const _Alloc& _Al) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Mode | ios_base::in, _Al) {} + + explicit basic_istringstream(_Mystr&& _Str, ios_base::openmode _Mode = ios_base::in) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_STD move(_Str), _Mode | ios_base::in) {} + + template + basic_istringstream(const basic_string<_Elem, _Traits, _Alloc2>& _Str, const _Alloc& _Al) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, ios_base::in, _Al) {} + + template + basic_istringstream(const basic_string<_Elem, _Traits, _Alloc2>& _Str, ios_base::openmode _Mode, const _Alloc& _Al) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode | ios_base::in, _Al) {} + + template , int> = 0> + explicit basic_istringstream( + const basic_string<_Elem, _Traits, _Alloc2>& _Str, ios_base::openmode _Mode = ios_base::in) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode | ios_base::in) {} +#endif // _HAS_CXX20 + basic_istringstream(basic_istringstream&& _Right) : _Mybase(_STD addressof(_Stringbuffer)) { _Assign_rv(_STD move(_Right)); } @@ -428,7 +618,12 @@ public: return const_cast<_Mysb*>(_STD addressof(_Stringbuffer)); } - _NODISCARD _Mystr str() const { +#if _HAS_CXX20 + _NODISCARD _Mystr str() const& +#else + _NODISCARD _Mystr str() const +#endif // _HAS_CXX20 + { return _Stringbuffer.str(); } @@ -436,6 +631,30 @@ public: _Stringbuffer.str(_Newstr); } +#if _HAS_CXX20 + template <_Allocator _Alloc2> + _NODISCARD basic_string<_Elem, _Traits, _Alloc2> str(const _Alloc2& _Al) const { + return _Stringbuffer.str(_Al); + } + + _NODISCARD _Mystr str() && { + return _STD move(_Stringbuffer).str(); + } + + _NODISCARD _Mystr_view view() const noexcept { + return _Stringbuffer.view(); + } + + template , int> = 0> + void str(const basic_string<_Elem, _Traits, _Alloc2>& _Newstr) { + _Stringbuffer.str(_Newstr); + } + + void str(_Mystr&& _Newstr) { + _Stringbuffer.str(_STD move(_Newstr)); + } +#endif // _HAS_CXX20 + private: _Mysb _Stringbuffer; }; @@ -454,6 +673,10 @@ public: using _Mysb = basic_stringbuf<_Elem, _Traits, _Alloc>; using _Mystr = basic_string<_Elem, _Traits, _Alloc>; +#if _HAS_CXX20 + using _Mystr_view = basic_string_view<_Elem, _Traits>; +#endif // _HAS_CXX20 + basic_ostringstream() : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(ios_base::out) {} explicit basic_ostringstream(ios_base::openmode _Mode) @@ -462,6 +685,27 @@ public: explicit basic_ostringstream(const _Mystr& _Str, ios_base::openmode _Mode = ios_base::out) : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode | ios_base::out) {} +#if _HAS_CXX20 + basic_ostringstream(ios_base::openmode _Mode, const _Alloc& _Al) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Mode | ios_base::out, _Al) {} + + explicit basic_ostringstream(_Mystr&& _Str, ios_base::openmode _Mode = ios_base::out) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_STD move(_Str), _Mode | ios_base::out) {} + + template + basic_ostringstream(const basic_string<_Elem, _Traits, _Alloc2>& _Str, const _Alloc& _Al) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, ios_base::out, _Al) {} + + template + basic_ostringstream(const basic_string<_Elem, _Traits, _Alloc2>& _Str, ios_base::openmode _Mode, const _Alloc& _Al) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode | ios_base::out, _Al) {} + + template , int> = 0> + explicit basic_ostringstream( + const basic_string<_Elem, _Traits, _Alloc2>& _Str, ios_base::openmode _Mode = ios_base::out) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode | ios_base::out) {} +#endif // _HAS_CXX20 + basic_ostringstream(basic_ostringstream&& _Right) : _Mybase(_STD addressof(_Stringbuffer)) { _Assign_rv(_STD move(_Right)); } @@ -494,7 +738,12 @@ public: return const_cast<_Mysb*>(_STD addressof(_Stringbuffer)); } - _NODISCARD _Mystr str() const { +#if _HAS_CXX20 + _NODISCARD _Mystr str() const& +#else + _NODISCARD _Mystr str() const +#endif // _HAS_CXX20 + { return _Stringbuffer.str(); } @@ -502,6 +751,30 @@ public: _Stringbuffer.str(_Newstr); } +#if _HAS_CXX20 + template <_Allocator _Alloc2> + _NODISCARD basic_string<_Elem, _Traits, _Alloc2> str(const _Alloc2& _Al) const { + return _Stringbuffer.str(_Al); + } + + _NODISCARD _Mystr str() && { + return _STD move(_Stringbuffer).str(); + } + + _NODISCARD _Mystr_view view() const noexcept { + return _Stringbuffer.view(); + } + + template , int> = 0> + void str(const basic_string<_Elem, _Traits, _Alloc2>& _Newstr) { + _Stringbuffer.str(_Newstr); + } + + void str(_Mystr&& _Newstr) { + _Stringbuffer.str(_STD move(_Newstr)); + } +#endif // _HAS_CXX20 + private: _Mysb _Stringbuffer; }; @@ -526,6 +799,10 @@ public: using _Mysb = basic_stringbuf<_Elem, _Traits, _Alloc>; using _Mystr = basic_string<_Elem, _Traits, _Alloc>; +#if _HAS_CXX20 + using _Mystr_view = basic_string_view<_Elem, _Traits>; +#endif // _HAS_CXX20 + basic_stringstream() : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(ios_base::in | ios_base::out) {} explicit basic_stringstream(ios_base::openmode _Mode) @@ -534,6 +811,27 @@ public: explicit basic_stringstream(const _Mystr& _Str, ios_base::openmode _Mode = ios_base::in | ios_base::out) : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode) {} +#if _HAS_CXX20 + basic_stringstream(ios_base::openmode _Mode, const _Alloc& _Al) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Mode, _Al) {} + + explicit basic_stringstream(_Mystr&& _Str, ios_base::openmode _Mode = ios_base::in | ios_base::out) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_STD move(_Str), _Mode) {} + + template + basic_stringstream(const basic_string<_Elem, _Traits, _Alloc2>& _Str, const _Alloc& _Al) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, ios_base::in | ios_base::out, _Al) {} + + template + basic_stringstream(const basic_string<_Elem, _Traits, _Alloc2>& _Str, ios_base::openmode _Mode, const _Alloc& _Al) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode, _Al) {} + + template , int> = 0> + explicit basic_stringstream( + const basic_string<_Elem, _Traits, _Alloc2>& _Str, ios_base::openmode _Mode = ios_base::in | ios_base::out) + : _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode) {} +#endif // _HAS_CXX20 + basic_stringstream(basic_stringstream&& _Right) : _Mybase(_STD addressof(_Stringbuffer)) { _Assign_rv(_STD move(_Right)); } @@ -566,7 +864,12 @@ public: return const_cast<_Mysb*>(_STD addressof(_Stringbuffer)); } - _NODISCARD _Mystr str() const { +#if _HAS_CXX20 + _NODISCARD _Mystr str() const& +#else + _NODISCARD _Mystr str() const +#endif // _HAS_CXX20 + { return _Stringbuffer.str(); } @@ -574,6 +877,30 @@ public: _Stringbuffer.str(_Newstr); } +#if _HAS_CXX20 + template <_Allocator _Alloc2> + _NODISCARD basic_string<_Elem, _Traits, _Alloc2> str(const _Alloc2& _Al) const { + return _Stringbuffer.str(_Al); + } + + _NODISCARD _Mystr str() && { + return _STD move(_Stringbuffer).str(); + } + + _NODISCARD _Mystr_view view() const noexcept { + return _Stringbuffer.view(); + } + + template , int> = 0> + void str(const basic_string<_Elem, _Traits, _Alloc2>& _Newstr) { + _Stringbuffer.str(_Newstr); + } + + void str(_Mystr&& _Newstr) { + _Stringbuffer.str(_STD move(_Newstr)); + } +#endif // _HAS_CXX20 + private: _Mysb _Stringbuffer; }; diff --git a/stl/inc/xstring b/stl/inc/xstring index 00a4d2354b1..80355987c2c 100644 --- a/stl/inc/xstring +++ b/stl/inc/xstring @@ -2272,6 +2272,11 @@ struct _String_constructor_concat_tag { explicit _String_constructor_concat_tag() = default; }; +struct _String_constructor_rvalue_allocator_tag { + // tag to select constructors used by basic_stringbuf's rvalue str() + explicit _String_constructor_rvalue_allocator_tag() = default; +}; + [[noreturn]] inline void _Xlen_string() { _Xlength_error("string too long"); } @@ -2280,6 +2285,7 @@ template , class _Alloc = alloca class basic_string { // null-terminated transparent array of elements private: friend _Tidy_deallocate_guard; + friend basic_stringbuf<_Elem, _Traits, _Alloc>; using _Alty = _Rebind_alloc_t<_Alloc, _Elem>; using _Alty_traits = allocator_traits<_Alty>; @@ -2633,6 +2639,14 @@ public: } #endif // _HAS_CXX17 +#if _HAS_CXX20 + basic_string(_String_constructor_rvalue_allocator_tag, _Alloc&& _Al) + : _Mypair(_One_then_variadic_args_t{}, _STD move(_Al)) { + _Mypair._Myval2._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal())); + _Tidy_init(); + } +#endif // _HAS_CXX20 + private: void _Move_assign(basic_string& _Right, _Equal_allocators) noexcept { _Tidy_deallocate(); @@ -2662,6 +2676,51 @@ private: } public: +#if _HAS_CXX20 + bool _Move_assign_from_buffer(_Elem* const _Right, const size_type _Size, const size_type _Res) { + // Move assign from a buffer, used by basic_stringbuf; returns _Large_string_engaged() + _Tidy_deallocate(); + pointer _Fancy_right = _Refancy(_Right); + auto& _My_data = _Mypair._Myval2; + _My_data._Mysize = _Size; + _My_data._Myres = _Res - 1; + if (_My_data._Large_string_engaged()) { + _Construct_in_place(_My_data._Bx._Ptr, _Fancy_right); + return true; + } else { + _Traits::copy(_My_data._Bx._Buf, _Right, _Res); + _My_data._Myres = _BUF_SIZE - 1; + return false; + } + } + + // No instance of this type can exist where an exception may be thrown. + struct _Released_buffer { + pointer _Ptr; + size_type _Size; + size_type _Res; + }; + + _NODISCARD _Released_buffer _Release_to_buffer(_Alloc& _Al) { + // Release to a buffer, or allocate a new one if in small string mode + _Released_buffer _Result; + auto& _My_data = _Mypair._Myval2; + _Result._Size = _My_data._Mysize; + if (_My_data._Large_string_engaged()) { + _Result._Ptr = _My_data._Bx._Ptr; + _Result._Res = _My_data._Myres + 1; + } else { + // use _BUF_SIZE + 1 to avoid SSO, if the buffer is assigned back + _Result._Ptr = _Al.allocate(_BUF_SIZE + 1); + _Traits::copy(_Unfancy(_Result._Ptr), _My_data._Bx._Buf, _BUF_SIZE); + _Result._Res = _BUF_SIZE + 1; + } + _My_data._Orphan_all(); + _Tidy_init(); + return _Result; + } +#endif // _HAS_CXX20 + basic_string& operator=(basic_string&& _Right) noexcept(noexcept(_Move_assign(_Right, _Choose_pocma<_Alty>{}))) { if (this != _STD addressof(_Right)) { _Move_assign(_Right, _Choose_pocma<_Alty>{}); diff --git a/stl/inc/xutility b/stl/inc/xutility index f484b23b1f9..9f1c687ad01 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -1441,6 +1441,11 @@ using _Enable_if_execution_policy_t = typename remove_reference_t<_ExPo>::_Stand #endif // _HAS_CXX17 +#if _HAS_CXX20 +template +concept _Allocator = _Is_allocator<_Ty>::value; +#endif // _HAS_CXX20 + // FUNCTION TEMPLATE _Idl_distance template _NODISCARD constexpr auto _Idl_distance(const _Iter& _First, const _Iter& _Last) { diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 28011748cb5..e37d8daf036 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -140,6 +140,7 @@ // P0339R6 polymorphic_allocator<> // P0356R5 bind_front() // P0357R3 Supporting Incomplete Types In reference_wrapper +// P0408R7 Efficient Access To basic_stringbuf's Buffer // P0415R1 constexpr For (Again) // P0439R0 enum class memory_order // P0457R2 starts_with()/ends_with() For basic_string/basic_string_view diff --git a/tests/std/test.lst b/tests/std/test.lst index acdf8d9b478..014d2c798d4 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -229,6 +229,7 @@ tests\P0339R6_polymorphic_allocator tests\P0355R7_calendars_and_time_zones_io tests\P0356R5_bind_front tests\P0357R3_supporting_incomplete_types_in_reference_wrapper +tests\P0408R7_efficient_access_to_stringbuf_buffer tests\P0414R2_shared_ptr_for_arrays tests\P0415R1_constexpr_complex tests\P0426R1_constexpr_char_traits diff --git a/tests/std/tests/P0408R7_efficient_access_to_stringbuf_buffer/env.lst b/tests/std/tests/P0408R7_efficient_access_to_stringbuf_buffer/env.lst new file mode 100644 index 00000000000..e5b00aee0d4 --- /dev/null +++ b/tests/std/tests/P0408R7_efficient_access_to_stringbuf_buffer/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_winsdk_matrix.lst diff --git a/tests/std/tests/P0408R7_efficient_access_to_stringbuf_buffer/test.cpp b/tests/std/tests/P0408R7_efficient_access_to_stringbuf_buffer/test.cpp new file mode 100644 index 00000000000..84d4474aa25 --- /dev/null +++ b/tests/std/tests/P0408R7_efficient_access_to_stringbuf_buffer/test.cpp @@ -0,0 +1,302 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +constexpr char empty_string[] = ""; +constexpr char small_string[] = "a"; +constexpr char large_string[] = + "This is a long long long long long long long long string to avoid small string optimization."; + +template +struct test_rvalue { + void operator()(const string& init_value) { + string buffer{init_value}; + size_t res = buffer.capacity(); + Stream stream{move(buffer)}; + assert(stream.view() == init_value); + assert(stream.str() == init_value); + assert(stream.view() == init_value); + assert(stream.rdbuf()->get_allocator() == init_value.get_allocator()); + // Move out the buffer, the underlying buffer should be empty. + buffer = move(stream).str(); + if (buffer == large_string) { + // stream doesn't actually have space for a null-terminator + assert(buffer.capacity() == res); + } else { + assert(buffer.capacity() == res + 1); + } + assert(buffer == init_value); + assert(stream.view().empty()); + assert(stream.str().empty()); + // Move in the buffer string + stream.str(move(buffer)); + assert(stream.view() == init_value); + assert(stream.str() == init_value); + assert(stream.rdbuf()->get_allocator() == init_value.get_allocator()); + // Move to another stream + Stream stream2 = move(stream); + assert(stream.view().empty()); + assert(stream2.view() == init_value); + } +}; + +template +struct test_allocator { + void operator()(const pmr::string& init_value) { + Stream stream{init_value}; + assert(stream.view() == init_value); + assert(stream.str(init_value.get_allocator()) == init_value); + assert(stream.str() == string_view{init_value}); + assert(move(stream).str() == string_view{init_value}); + assert(stream.view().empty()); + stream.str(init_value); + assert(stream.view() == init_value); + assert(stream.str(init_value.get_allocator()) == init_value); + assert(stream.str() == string_view{init_value}); + } +}; + +template +struct test_pmr_allocator { + pmr::polymorphic_allocator alloc; + + void operator()(const pmr::string& init_value) { + Stream stream{init_value, alloc}; + assert(stream.rdbuf()->get_allocator() != init_value.get_allocator()); + { + pmr::string new_str = stream.str(init_value.get_allocator()); + assert(new_str == init_value); + assert(new_str.get_allocator() == init_value.get_allocator()); + } + { + pmr::string new_str = stream.str(); + assert(new_str == init_value); + assert(new_str.get_allocator() != init_value.get_allocator()); + } + stream.str(init_value); + assert(stream.rdbuf()->get_allocator() != init_value.get_allocator()); + { + pmr::string new_str = stream.str(init_value.get_allocator()); + assert(new_str == init_value); + assert(new_str.get_allocator() == init_value.get_allocator()); + } + { + pmr::string new_str = stream.str(); + assert(new_str == init_value); + assert(new_str.get_allocator() != init_value.get_allocator()); + } + Stream stream2{init_value, init_value.get_allocator()}; + assert(stream2.rdbuf()->get_allocator() == init_value.get_allocator()); + } +}; + +template +void run_test_util() { + Test test{}; + test(empty_string); + test(small_string); + test(large_string); +} + +template