Skip to content
Closed
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
9 changes: 6 additions & 3 deletions include/beman/any_view/detail/iterator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ class iterator : public iterator_category_type<iterator_concept_t<OptsV>, std::i
static constexpr bool random_access = flag_is_set<OptsV, any_view_options::random_access>;
static constexpr bool contiguous = flag_is_set<OptsV, any_view_options::contiguous>;

using cache_type =
std::conditional_t<convertible_to_borrowed<rvalue_ref_t<RefT>, RValueRefT>, iter_cache_t<RefT>, no_cache>;
using cache_type = std::conditional_t<forward && std::is_lvalue_reference_v<RefT> &&
convertible_to_borrowed<rvalue_ref_t<RefT>, RValueRefT>,
iter_cache_t<RefT>,
no_cache>;
using polymorphic_type = polymorphic_iterator<RefT, RValueRefT, DiffT, OptsV>;

static constexpr bool has_cache = not std::is_same_v<cache_type, no_cache>;
Expand Down Expand Up @@ -262,9 +264,10 @@ class iterator : public iterator_category_type<iterator_concept_t<OptsV>, std::i
}

[[nodiscard]] constexpr bool operator==(std::default_sentinel_t) const {
// sentinel comparison must dispatch for a contiguous iterator
if constexpr (has_cache and not contiguous) {
return cache_or_index == cache_type{};
} else if constexpr (has_index) {
return dispatch<sentinel_compare_at_t<DiffT>>(poly, cache_or_index);
} else {
return dispatch<sentinel_compare_t>(poly);
}
Expand Down
25 changes: 8 additions & 17 deletions include/beman/any_view/detail/polymorphic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,46 +51,37 @@ class basic_polymorphic {
constexpr ~basic_polymorphic() { entry(destroy_t<StorageT>{})(storage); }

constexpr basic_polymorphic& operator=(const basic_polymorphic& other) {
if (this == std::addressof(other)) {
return *this;
if (this != std::addressof(other)) {
*this = basic_polymorphic(other);
}

std::destroy_at(this);
std::construct_at(this, other);
return *this;
}

constexpr basic_polymorphic& operator=(basic_polymorphic&& other) noexcept {
if (this == std::addressof(other)) {
return *this;
if (this != std::addressof(other)) {
std::destroy_at(this);
std::construct_at(this, std::move(other));
}

std::destroy_at(this);
std::construct_at(this, std::move(other));
Comment on lines -64 to -69

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This change adds nothing

return *this;
}

template <adaptor AdaptorT>
constexpr basic_polymorphic& operator=(AdaptorT&& adaptor) {
std::destroy_at(this);
std::construct_at(this, std::forward<AdaptorT>(adaptor));
return *this;
return *this = basic_polymorphic(std::forward<AdaptorT>(adaptor));
}

// converting assignment

template <std::derived_from<ProtocolTs>... OtherProtocolTs>
constexpr basic_polymorphic& operator=(const basic_polymorphic<StorageT, OtherProtocolTs...>& other) {
std::destroy_at(this);
std::construct_at(this, other);
return *this;
return *this = basic_polymorphic(other);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For this and converting move assignment, would prefer to use explicit template parameters on basic_polymorphic to avoid relying on injected-class-name which could reasonably be interpreted as intending to perform CTAD, even though that's not the case here.

}

template <std::derived_from<ProtocolTs>... OtherProtocolTs>
constexpr basic_polymorphic& operator=(basic_polymorphic<StorageT, OtherProtocolTs...>&& other) noexcept {
std::destroy_at(this);
std::construct_at(this, std::move(other));
return *this;
return *this = basic_polymorphic(std::move(other));
}

constexpr StorageT& get() & noexcept { return storage; }
Expand Down
37 changes: 27 additions & 10 deletions include/beman/any_view/detail/polymorphic_iterator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ struct sentinel_compare_t : unary_protocol {
}
};

template <class DiffT>
struct sentinel_compare_at_t : unary_protocol {
template <not_adaptor T>
static bool fn(const T& self, DiffT offset);

template <adaptor IteratorAdaptorT>
[[nodiscard]] static constexpr bool fn(const IteratorAdaptorT& adaptor, DiffT offset) {
return adaptor.iterator + offset == adaptor.sentinel;
}
};

template <class RefT>
struct dereference_t : unary_protocol {
template <not_adaptor T>
Expand Down Expand Up @@ -243,22 +254,27 @@ struct rvalue_ref<T&> {
template <class T>
using rvalue_ref_t = typename rvalue_ref<T>::type;

template <class RefT, class RValueRefT>
struct input_cache_protocol : inherit<dereference_t<RefT>, iter_move_t<RValueRefT>, increment_t> {};

template <has_cache RefT, class RValueRefT>
requires convertible_to_borrowed<rvalue_ref_t<RefT>, RValueRefT>
struct input_cache_protocol<RefT, RValueRefT> : inherit<cache_t<RefT>, next_t<RefT>> {};
Comment on lines -246 to -251

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This and related changes to restrict where caching is applied need to be reverted. *i is required to be equality-preserving on an iterator, so it is semantically valid to cache it. It does not need to be a forward iterator.


template <class RefT, class RValueRefT>
struct input_protocol : inherit<move_t<iterator_storage>,
destroy_t<iterator_storage>,
input_cache_protocol<RefT, RValueRefT>,
dereference_t<RefT>,
iter_move_t<RValueRefT>,
increment_t,
sentinel_compare_t> {};

template <class RefT, class RValueRefT>
struct forward_protocol
: inherit<input_protocol<RefT, RValueRefT>, copy_t<iterator_storage>, type_t, equality_compare_t> {};
struct forward_cache_protocol : inherit<> {};

template <has_cache RefT, class RValueRefT>
requires convertible_to_borrowed<rvalue_ref_t<RefT>, RValueRefT>
struct forward_cache_protocol<RefT, RValueRefT> : inherit<cache_t<RefT>, next_t<RefT>> {};

template <class RefT, class RValueRefT>
struct forward_protocol : inherit<input_protocol<RefT, RValueRefT>,
copy_t<iterator_storage>,
forward_cache_protocol<RefT, RValueRefT>,
type_t,
equality_compare_t> {};

template <class RefT>
struct bidirectional_cache_protocol : inherit<decrement_t> {};
Expand All @@ -278,6 +294,7 @@ struct random_access_cache_protocol<RefT, RValueRefT, DiffT> : inherit<advance_t
template <class RefT, class RValueRefT, class DiffT>
struct random_access_protocol : inherit<bidirectional_protocol<RefT, RValueRefT>,
random_access_cache_protocol<RefT, RValueRefT, DiffT>,
sentinel_compare_at_t<DiffT>,
three_way_compare_t,
subtract_t<DiffT>> {};

Expand Down
2 changes: 1 addition & 1 deletion tests/beman/any_view/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ endfunction()
beman_add_benchmark(all ${BENCHMARK_DETAIL_SOURCES})
beman_add_benchmark(take ${BENCHMARK_DETAIL_SOURCES})

beman_add_tests(concepts constexpr iterator sfinae type_traits)
beman_add_tests(concepts constexpr iterator sfinae type_traits regressions)
6 changes: 3 additions & 3 deletions tests/beman/any_view/constexpr.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ template <class T>
non_trivially_copyable<T>::non_trivially_copyable(const non_trivially_copyable&) = default;

constexpr auto set_front(any_view<int, forward> view, int value) {
// lvalue reference uses cache object to fuse virtual dispatches
static_assert(sizeof(std::ranges::iterator_t<any_view<int>>) ==
sizeof(std::ranges::iterator_t<proxy_any_view<non_trivially_copyable<int>>>) + sizeof(int*));
// Only forward and stronger iterators cache lvalue references.
static_assert(sizeof(std::ranges::iterator_t<any_view<int, forward>>) ==
sizeof(std::ranges::iterator_t<any_view<int>>) + sizeof(int*));

auto& ref = view.front();
// even with cache object, lifetime of reference is not tied to lifetime of iterator
Expand Down
82 changes: 82 additions & 0 deletions tests/beman/any_view/detail/self_ref_forward_proxy_view.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#pragma once

#include <cstddef>
#include <iterator>
#include <ranges>
#include <type_traits>
#include <utility>

struct self_ref_forward_proxy {
const int* pointer{};

operator int() const noexcept { return *pointer; }
};

static_assert(std::is_trivially_copyable_v<self_ref_forward_proxy>);

struct self_ref_forward_proxy_iterator {
using value_type = int;
using difference_type = std::ptrdiff_t;
using iterator_concept = std::forward_iterator_tag;

mutable int value{};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you clarify why mutable is here? I'm not sure I see where this is needed.

int stop{};

self_ref_forward_proxy_iterator() = default;

self_ref_forward_proxy_iterator(int value, int stop) : value(value), stop(stop) {}

self_ref_forward_proxy_iterator(const self_ref_forward_proxy_iterator&) = default;

self_ref_forward_proxy_iterator& operator=(const self_ref_forward_proxy_iterator&) = default;

self_ref_forward_proxy_iterator(self_ref_forward_proxy_iterator&& other) noexcept
: value(other.value), stop(other.stop) {
other.value = -777;
}

self_ref_forward_proxy_iterator& operator=(self_ref_forward_proxy_iterator&& other) noexcept {
value = other.value;
stop = other.stop;

other.value = -777;
return *this;
}

self_ref_forward_proxy operator*() const noexcept {
// The proxy points into this iterator.
return {&value};
}

self_ref_forward_proxy_iterator& operator++() noexcept {
++value;
return *this;
}

self_ref_forward_proxy_iterator operator++(int) noexcept {
auto previous = *this;
++*this;
return previous;
}

friend bool operator==(const self_ref_forward_proxy_iterator&, const self_ref_forward_proxy_iterator&) = default;
};

static_assert(std::forward_iterator<self_ref_forward_proxy_iterator>);

struct self_ref_forward_proxy_view : std::ranges::view_base {
int first{};
int stop{};

self_ref_forward_proxy_view() = default;

self_ref_forward_proxy_view(int first, int stop) : first(first), stop(stop) {}

self_ref_forward_proxy_iterator begin() const noexcept { return {first, stop}; }

self_ref_forward_proxy_iterator end() const noexcept { return {stop, stop}; }
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can be subrange<self_ref_forward_proxy_iterator>


static_assert(std::ranges::forward_range<self_ref_forward_proxy_view>);
66 changes: 66 additions & 0 deletions tests/beman/any_view/detail/self_ref_input_view.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#pragma once

#include <beman/any_view/any_view.hpp>

struct self_ref_input_iterator {
using value_type = int;
using difference_type = std::ptrdiff_t;
using iterator_concept = std::input_iterator_tag;

mutable int value{};
int stop{};

self_ref_input_iterator() = delete;

self_ref_input_iterator(int value, int stop) : value(value), stop(stop) {}

self_ref_input_iterator(const self_ref_input_iterator&) = delete;
self_ref_input_iterator& operator=(const self_ref_input_iterator&) = delete;

self_ref_input_iterator(self_ref_input_iterator&& other) noexcept : value(other.value), stop(other.stop) {
other.value = -777;
}

self_ref_input_iterator& operator=(self_ref_input_iterator&& other) noexcept {
value = other.value;
stop = other.stop;

other.value = -777;
return *this;
}

int& operator*() const noexcept {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm sure you could make your point with const int& without mutable here.

// The returned reference points inside this iterator.
return value;
}

self_ref_input_iterator& operator++() noexcept {
++value;
return *this;
}

void operator++(int) noexcept { ++*this; }

friend bool operator==(const self_ref_input_iterator& iterator, std::default_sentinel_t) noexcept {
return iterator.value == iterator.stop;
}
};

static_assert(std::input_iterator<self_ref_input_iterator>);

struct self_ref_input_view : std::ranges::view_base {
int first{};
int stop{};

self_ref_input_view() = default;

self_ref_input_view(int first, int stop) : first(first), stop(stop) {}

self_ref_input_iterator begin() { return {first, stop}; }

std::default_sentinel_t end() const noexcept { return {}; }
};
Comment on lines +53 to +64

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can be subrange<self_ref_input_iterator, std::default_sentinel_t>


static_assert(std::ranges::input_range<self_ref_input_view>);
86 changes: 86 additions & 0 deletions tests/beman/any_view/detail/throwing_forward_view.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#pragma once

#include <cstddef>
#include <iterator>
#include <ranges>
#include <utility>

struct throwing_copy_error {};

struct throwing_forward_iterator {
using value_type = int;
using difference_type = std::ptrdiff_t;
using iterator_concept = std::forward_iterator_tag;

inline static bool throw_on_copy = false;
inline static int live_count = 0;

int* current{};

throwing_forward_iterator() noexcept { ++live_count; }

explicit throwing_forward_iterator(int* current) noexcept : current(current) { ++live_count; }

throwing_forward_iterator(const throwing_forward_iterator& other) : current(other.current) {
if (throw_on_copy) {
throw throwing_copy_error{};
}

++live_count;
}

throwing_forward_iterator(throwing_forward_iterator&& other) noexcept
: current(std::exchange(other.current, nullptr)) {
++live_count;
}

throwing_forward_iterator& operator=(const throwing_forward_iterator& other) {
if (throw_on_copy) {
throw throwing_copy_error{};
}

current = other.current;
return *this;
}

throwing_forward_iterator& operator=(throwing_forward_iterator&& other) noexcept {
current = std::exchange(other.current, nullptr);
return *this;
}

~throwing_forward_iterator() { --live_count; }

int& operator*() const noexcept { return *current; }

throwing_forward_iterator& operator++() noexcept {
++current;
return *this;
}

throwing_forward_iterator operator++(int) {
auto previous = *this;
++*this;
return previous;
}

friend bool operator==(const throwing_forward_iterator&, const throwing_forward_iterator&) = default;
};

static_assert(std::forward_iterator<throwing_forward_iterator>);

struct throwing_forward_view : std::ranges::view_base {
int* first{};
int* last{};

throwing_forward_view() = default;

throwing_forward_view(int* first, int* last) : first(first), last(last) {}

throwing_forward_iterator begin() const noexcept { return throwing_forward_iterator{first}; }

throwing_forward_iterator end() const noexcept { return throwing_forward_iterator{last}; }
};
Comment on lines +73 to +84

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can just be subrange<throwing_forward_iterator>


static_assert(std::ranges::forward_range<throwing_forward_view>);
Loading
Loading