diff --git a/stl/inc/ranges b/stl/inc/ranges index 776e1310efe..9e54f51b2cb 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -11,6 +11,7 @@ #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 @@ -634,6 +635,99 @@ namespace ranges { inline constexpr _Single_fn single; } // namespace views + // CLASS TEMPLATE ranges::istream_view + template + concept _Stream_extractable = requires(basic_istream<_Elem, _Traits>& __is, _Ty& __t) { + __is >> __t; + }; + + // clang-format off + template > + requires default_initializable<_Ty> && _Stream_extractable<_Ty, _Elem, _Traits> + class basic_istream_view : public view_interface> { + // clang-format on + private: + class _Iterator { + private: + basic_istream_view* _Parent = nullptr; + + public: + using iterator_concept = input_iterator_tag; + using difference_type = ptrdiff_t; + using value_type = _Ty; + + _Iterator() = default; + constexpr explicit _Iterator(basic_istream_view& _Parent_) noexcept : _Parent{_STD addressof(_Parent_)} {} + + _Iterator(const _Iterator&) = delete; + _Iterator(_Iterator&&) = default; + + _Iterator& operator=(const _Iterator&) = delete; + _Iterator& operator=(_Iterator&&) = default; + + _Iterator& operator++() { +#if _ITERATOR_DEBUG_LEVEL != 0 + // Per LWG-3489 + _STL_VERIFY(_Parent != nullptr, "cannot increment default-initialized istream_view iterator"); + _STL_VERIFY( + _Parent->_Stream != nullptr, "cannot increment istream_view iterator with uninitialized stream"); + _STL_VERIFY(!_Parent->_Stream_at_end(), "cannot increment istream_view iterator at end of stream"); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + *_Parent->_Stream >> _Parent->_Val; + return *this; + } + + void operator++(int) { + ++*this; + } + + _NODISCARD _Ty& operator*() const noexcept /* strengthened */ { +#if _ITERATOR_DEBUG_LEVEL != 0 + // Per LWG-3489 + _STL_VERIFY(_Parent != nullptr, "cannot dereference default-initialized istream_view iterator"); + _STL_VERIFY( + _Parent->_Stream != nullptr, "cannot dereference istream_view iterator with uninitialized stream"); + _STL_VERIFY(!_Parent->_Stream_at_end(), "cannot dereference istream_view iterator at end of stream"); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + return _Parent->_Val; + } + + _NODISCARD friend bool operator==(const _Iterator& _Left, default_sentinel_t) noexcept /* strengthened */ { + return _Left._Parent == nullptr || _Left._Parent->_Stream_at_end(); + } + }; + + basic_istream<_Elem, _Traits>* _Stream = nullptr; + _Ty _Val = _Ty{}; + + public: + basic_istream_view() = default; + constexpr explicit basic_istream_view(basic_istream<_Elem, _Traits>& _Stream_) noexcept( + is_nothrow_default_constructible_v<_Ty>) // strengthened + : _Stream{_STD addressof(_Stream_)} {} + + _NODISCARD constexpr auto begin() { + if (_Stream) { + *_Stream >> _Val; + } + return _Iterator{*this}; + } + + _NODISCARD constexpr default_sentinel_t end() const noexcept { + return default_sentinel; + } + + _NODISCARD constexpr bool _Stream_at_end() const noexcept { + return !*_Stream; + } + }; + + template + _NODISCARD basic_istream_view<_Ty, _Elem, _Traits> istream_view(basic_istream<_Elem, _Traits>& _Stream) noexcept( + is_nothrow_default_constructible_v<_Ty>) /* strengthened */ { + return basic_istream_view<_Ty, _Elem, _Traits>{_Stream}; + } + // CLASS TEMPLATE ranges::ref_view // clang-format off template diff --git a/tests/std/test.lst b/tests/std/test.lst index ce4aff5bb8d..f5eb53b2b7e 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -253,6 +253,8 @@ tests\P0896R4_common_iterator tests\P0896R4_common_iterator_death tests\P0896R4_counted_iterator tests\P0896R4_counted_iterator_death +tests\P0896R4_istream_view +tests\P0896R4_istream_view_death tests\P0896R4_P1614R2_comparisons tests\P0896R4_ranges_alg_adjacent_find tests\P0896R4_ranges_alg_all_of diff --git a/tests/std/tests/P0896R4_istream_view/env.lst b/tests/std/tests/P0896R4_istream_view/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_istream_view/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/P0896R4_istream_view/test.cpp b/tests/std/tests/P0896R4_istream_view/test.cpp new file mode 100644 index 00000000000..20d8efb5f4b --- /dev/null +++ b/tests/std/tests/P0896R4_istream_view/test.cpp @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +constexpr int expected_empty[] = {-1, -1, -1, -1, -1}; +constexpr int expected[] = {0, 1, 2, 3, -1}; + +struct streamable { + streamable() = default; + streamable(const int input) : _val(input) {} + + friend istream& operator>>(istream& is, streamable& right) noexcept { + is >> right._val; + return is; + } + + friend bool operator==(const streamable& left, const streamable& right) noexcept = default; + + int _val = 0; +}; + +template +void test_one_type() { + using ranges::basic_istream_view; + + // validate type properties + using R = basic_istream_view; + static_assert(ranges::view); + static_assert(ranges::input_range); + static_assert(!ranges::forward_range); + + static_assert(!ranges::sized_range); + static_assert(!ranges::common_range); + + // validate constructors + istringstream nonempty_stream{"0"}; + istringstream empty_intstream{}; + R default_constructed{}; + R empty_constructed{empty_intstream}; + R non_empty_constructed{nonempty_stream}; + + static_assert(is_nothrow_constructible_v == is_nothrow_default_constructible_v); + static_assert(is_nothrow_constructible_v == is_nothrow_default_constructible_v); + + // validate member begin + // NOTE: begin() consumes the first token + (void) default_constructed.begin(); // default-constructed basic_istream_view doesn't model range. + assert(empty_constructed.begin() == default_sentinel); + assert(non_empty_constructed.begin() != default_sentinel); + + // validate default constructed istream::iterator + { + const ranges::iterator_t default_constructed_it; + assert(default_constructed_it == default_sentinel); + static_assert(noexcept(default_constructed_it == default_sentinel)); + } + + // validate member end + static_assert(same_as); + static_assert(noexcept(default_constructed.end())); + static_assert(noexcept(ranges::end(default_constructed))); + + // Non existing member functions + static_assert(!CanMemberSize); + static_assert(!CanMemberData); + static_assert(!CanMemberEmpty); + static_assert(!CanMemberFront); + static_assert(!CanMemberBack); + + // Some basic tests + T input_empty[] = {-1, -1, -1, -1, -1}; + ranges::copy(empty_constructed, input_empty); + assert(ranges::equal(input_empty, expected_empty)); + + istringstream intstream{"0 1 2 3"}; + T input_value[] = {-1, -1, -1, -1, -1}; + ranges::copy(basic_istream_view{intstream}, input_value); + assert(ranges::equal(input_value, expected)); + + istringstream intstream_view{"0 1 2 3"}; + T input_value_view[] = {-1, -1, -1, -1, -1}; + ranges::copy(ranges::istream_view(intstream_view), input_value_view); + static_assert(noexcept(ranges::istream_view(intstream_view))); + assert(ranges::equal(input_value_view, expected)); +} + +istringstream some_stream{"42"}; +constexpr bool test_constexpr() { + // Default constructor is constexpr + ranges::basic_istream_view empty{}; + + // begin is constexpr??!? + (void) empty.begin(); + + // stream constructor is constexpr + ranges::basic_istream_view meow{some_stream}; + + // end is constexpr + (void) meow.end(); + + return true; +} + +int main() { + test_one_type(); + test_one_type(); + + static_assert(test_constexpr()); +} diff --git a/tests/std/tests/P0896R4_istream_view_death/env.lst b/tests/std/tests/P0896R4_istream_view_death/env.lst new file mode 100644 index 00000000000..22f1f0230a4 --- /dev/null +++ b/tests/std/tests/P0896R4_istream_view_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/P0896R4_istream_view_death/test.cpp b/tests/std/tests/P0896R4_istream_view_death/test.cpp new file mode 100644 index 00000000000..22f76db2769 --- /dev/null +++ b/tests/std/tests/P0896R4_istream_view_death/test.cpp @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define _CONTAINER_DEBUG_LEVEL 1 + +#include +#include +#include +#include + +#include +using namespace std; + +using iview = ranges::basic_istream_view; + +void test_preincrement_default_initialized() { + ranges::iterator_t it; + (void) ++it; +} + +void test_postincrement_default_initialized() { + ranges::iterator_t it; + (void) it++; +} + +void test_dereference_default_initialized() { + ranges::iterator_t it; + (void) *it; +} + +void test_preincrement_no_stream() { + iview v; + auto it = v.begin(); + (void) ++it; +} + +void test_postincrement_no_stream() { + iview v; + auto it = v.begin(); + (void) it++; +} + +void test_dereference_no_stream() { + iview v; + auto it = v.begin(); + (void) *it; +} + +void test_compare_no_stream() { + iview v; + auto it = v.begin(); + auto se = v.end(); + (void) (it == se); +} + +void test_preincrement_end_of_stream() { + istringstream stream; + iview view{stream}; + auto it = view.begin(); + (void) ++it; +} + +void test_postincrement_end_of_stream() { + istringstream stream; + iview view{stream}; + auto it = view.begin(); + (void) it++; +} + +void test_dereference_end_of_stream() { + istringstream stream; + iview view{stream}; + auto it = view.begin(); + (void) *it; +} + +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec; + +#if _ITERATOR_DEBUG_LEVEL != 0 + exec.add_death_tests({ + test_preincrement_default_initialized, + test_postincrement_default_initialized, + test_dereference_default_initialized, + test_preincrement_no_stream, + test_postincrement_no_stream, + test_dereference_no_stream, + test_compare_no_stream, + test_preincrement_end_of_stream, + test_postincrement_end_of_stream, + test_dereference_end_of_stream, + }); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + + return exec.run(argc, argv); +}