I made this input iterator
template <typename T>
struct input_iter: base_it<T> {
private:
typename base_it<T>::pointer _ptr;
public:
constexpr input_iter<T>() = default;
constexpr explicit input_iter<T>(typename base_it<T>::pointer ptr = nullptr)
: _ptr { ptr } {}
constexpr ~input_iter<T>() = default;
constexpr input_iter<T>(const input_iter<T>& other) = default;
constexpr input_iter<T>(input_iter<T>&& other) noexcept = default;
[[nodiscard]]
constexpr auto operator=(typename base_it<T>::pointer ptr) -> input_iter<T>& {
_ptr = ptr; return *this;
}
constexpr auto operator=(const input_iter<T>&) -> input_iter<T>& = default;
constexpr auto operator=(input_iter<T>&&) noexcept -> input_iter<T>& = default;
[[nodiscard]]
constexpr auto operator*() const noexcept -> const typename base_it<T>::reference {
return *_ptr;
}
[[nodiscard]]
constexpr auto operator->() const noexcept -> const typename base_it<T>::pointer {
return _ptr;
}
constexpr auto operator++() noexcept -> input_iter& {
++this-> _ptr;
return *this;
}
constexpr void operator++(int) noexcept {
++(*this);
}
[[nodiscard]]
constexpr friend auto operator==(const input_iter& lhs, const input_iter& rhs) noexcept -> bool {
return lhs._ptr == rhs._ptr;
}
[[nodiscard]]
constexpr friend auto operator!=(const input_iter& lhs, const input_iter& rhs) noexcept -> bool {
return not (lhs == rhs);
}
};
Where you must assume (for now) that base_iter
is std::iterator
.
Well, one mandatory requirement for an input_iterator
is that must be indirectly_readable
. Since my input_iter
doesn't belong to any concrete datastructure, and is in a module by itself, because I want to make it available for containers or ranges which is elements are stored in contiguous memory locations (but that's an story for another SO post) I would like to constraint the operation of writing things to the underlying container or range. So, my idea is the following:
template <typename T>
using base_it = std::iterator<std::input_iterator_tag, const T>;
Note the const T, not T in the alias
So whenever I try to write an stamement of this kind:
collections::Array arr = collections::Array<int, 5>{1, 2, 3, 4, 5};
auto it_begin = arr.begin();
*it_begin = 7;
I am receving the error that I want, at compile time!
.\zero\tests\iterators\legacy\legacy_iterator_tests.cpp:47:28: error: cannot assign to return value because function
'operator*' returns a const value
*it_begin = 7;
Fine. But...
static_assert
and check that that expression doesn't compiles? I've seen complicated stuff going on with lambdas in the else block on an if constexpr
, and some more using SFINAE
, but I am not able to write it by myself. Can someone explain how to write it?Code below is what I've tried so far, without obviously, no success. Take it as a meta-idea.
if constexpr (some_cond)
static_assert(
*it_begin = 7,
"Wait... this is compiling! This shouldn't happen, since an input iterator musn't be able to performn write operations");
As for the static assert: why not just use a concept if you're using C++20?
{*d = from}
is the expression that has to compile while
-> std::convertible_to<decltype(*d)>;
part is the optional type of that expression. It can be made to match the exact type with same_as
, or can be just removed in case the type is irrelevant.
#include <concepts>
#include <vector> //just for demonstration purposes
template<typename Dereferencable, typename From>
concept DereferencedAssignable = requires(Dereferencable d, From from)
{
{*d = from} -> std::convertible_to<decltype(*d)>; //or std::same_as, depending on one's needs
};
static_assert(DereferencedAssignable<int*, int>);
static_assert(not DereferencedAssignable<const int*, int>);
static_assert(DereferencedAssignable<std::vector<double>::iterator, double>);
static_assert(not DereferencedAssignable<std::vector<double>::const_iterator, double>);
https://godbolt.org/z/8s44Gb3ME
As for being idiomatic: I would opt for returning a copy of the previous value in the the postfix incrementation operator, but that might have been just a minor oversight here.