Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ranges::istream_view #1334

Merged
merged 9 commits into from
Oct 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions stl/inc/ranges
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#ifndef __cpp_lib_concepts
#pragma message("The contents of <ranges> are available only with C++20 concepts support.")
#else // ^^^ !defined(__cpp_lib_concepts) / defined(__cpp_lib_concepts) vvv
#include <iosfwd>
#include <iterator>
#include <span>
#include <string_view>
Expand Down Expand Up @@ -634,6 +635,99 @@ namespace ranges {
inline constexpr _Single_fn single;
} // namespace views

// CLASS TEMPLATE ranges::istream_view
template <class _Ty, class _Elem, class _Traits>
concept _Stream_extractable = requires(basic_istream<_Elem, _Traits>& __is, _Ty& __t) {
__is >> __t;
};

// clang-format off
template <movable _Ty, class _Elem, class _Traits = char_traits<_Elem>>
requires default_initializable<_Ty> && _Stream_extractable<_Ty, _Elem, _Traits>
class basic_istream_view : public view_interface<basic_istream_view<_Ty, _Elem, _Traits>> {
// 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{};
CaseyCarter marked this conversation as resolved.
Show resolved Hide resolved

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 <class _Ty, class _Elem, class _Traits>
_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 <range _Rng>
Expand Down
2 changes: 2 additions & 0 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/std/tests/P0896R4_istream_view/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_matrix.lst
120 changes: 120 additions & 0 deletions tests/std/tests/P0896R4_istream_view/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <iostream>
#include <ranges>
#include <sstream>
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
#include <type_traits>

#include <range_algorithm_support.hpp>

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 <class T>
void test_one_type() {
using ranges::basic_istream_view;

// validate type properties
using R = basic_istream_view<T, char>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I observe that this doesn't test character types other than char, much less custom traits, so there are certain kinds of product bugs that this could miss - however, the product code appears to be correct, so I don't think such test coverage is necessary to proceed. Something to keep in mind for the future, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the thought was that every wild character type would actually only implement the basic_istream functionality not the istream_view itself

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm mostly concerned about typos where we should have said _Elem but instead said char, so it doesn't work with wchar_t.

static_assert(ranges::view<R>);
static_assert(ranges::input_range<R>);
static_assert(!ranges::forward_range<R>);

static_assert(!ranges::sized_range<R>);
static_assert(!ranges::common_range<R>);

// 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<R> == is_nothrow_default_constructible_v<T>);
static_assert(is_nothrow_constructible_v<R, istream&> == is_nothrow_default_constructible_v<T>);

// 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<R> default_constructed_it;
assert(default_constructed_it == default_sentinel);
static_assert(noexcept(default_constructed_it == default_sentinel));
}

// validate member end
static_assert(same_as<decltype(default_constructed.end()), default_sentinel_t>);
static_assert(noexcept(default_constructed.end()));
static_assert(noexcept(ranges::end(default_constructed)));

// Non existing member functions
static_assert(!CanMemberSize<R>);
static_assert(!CanMemberData<R>);
static_assert(!CanMemberEmpty<R>);
static_assert(!CanMemberFront<R>);
static_assert(!CanMemberBack<R>);

// 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<T, char>{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<T>(intstream_view), input_value_view);
static_assert(noexcept(ranges::istream_view<T>(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<int, char> empty{};

// begin is constexpr??!?
(void) empty.begin();

// stream constructor is constexpr
ranges::basic_istream_view<int, char> meow{some_stream};

// end is constexpr
(void) meow.end();

return true;
}

int main() {
test_one_type<int>();
test_one_type<streamable>();

static_assert(test_constexpr());
}
4 changes: 4 additions & 0 deletions tests/std/tests/P0896R4_istream_view_death/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\strict_winsdk_concepts_matrix.lst
96 changes: 96 additions & 0 deletions tests/std/tests/P0896R4_istream_view_death/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#define _CONTAINER_DEBUG_LEVEL 1

#include <cassert>
#include <cstddef>
#include <ranges>
#include <sstream>

#include <test_death.hpp>
using namespace std;

using iview = ranges::basic_istream_view<int, char>;

void test_preincrement_default_initialized() {
ranges::iterator_t<iview> it;
(void) ++it;
}

void test_postincrement_default_initialized() {
ranges::iterator_t<iview> it;
(void) it++;
}

void test_dereference_default_initialized() {
ranges::iterator_t<iview> 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);
}