I have tried to make std::optional
into a range, by making an iterator that just goes to one thing then breaks:
#include<ranges>
#include <iostream>
#include<optional>
namespace maybe {
template<class T>
class iterator_to_nowhere
{
public:
using difference_type = ptrdiff_t;
using value_type = T;
using reference = T&;
using pointer = T*;
using interator_category = std::forward_iterator_tag;
iterator_to_nowhere(iterator_to_nowhere<T> const& other) = default;
iterator_to_nowhere(T* p) : ptr(p) {};
~iterator_to_nowhere() = default;
iterator_to_nowhere<T>& operator=(const iterator_to_nowhere<T>&) = default;
iterator_to_nowhere<T>& operator () { invalidate(); return *this; }
iterator_to_nowhere<T> operator (int) { auto temp = *this; invalidate(); return temp; }
reference operator*() { return *ptr; }
T operator*() const { return *ptr; }
pointer operator->() const { return ptr; }
friend bool operator==(iterator_to_nowhere const& lhs, iterator_to_nowhere const& rhs) = default;
friend bool operator!=(iterator_to_nowhere const&, iterator_to_nowhere const&) = default;
friend void swap(iterator_to_nowhere<T>& lhs, iterator_to_nowhere<T>& rhs) { std::swap(lhs, rhs); }
private:
void invalidate() { ptr = nullptr; };
T* ptr;
};
...then implementing range things with that iterator on a derived class of std::optional
template<class T>
class optional : public std::optional<T>
{
using value_type = T;
using size_type = size_t;
using difference_type = ptrdiff_t;
using iterator = iterator_to_nowhere<T>;
using const_iterator = iterator_to_nowhere<T const>;
using sentinel = iterator;
public:
optional() = default;
optional(optional<T> const&) = default;
optional(optional<T>&&) = default;
optional(std::optional<T> const& val) : std::optional<T>(val) {};
optional(std::nullopt_t const& nullopt) : std::optional<T>(nullopt) {};
optional(T const& val) : std::optional<T>(val) {};
iterator begin() { return { raw() }; }
const_iterator cbegin() { return { raw() }; }
iterator end() { return { nullptr }; }
const_iterator cend() { return { nullptr }; }
size_t size() const { return size_t(this->has_value()); };
private:
T* raw() { return (this->has_value()) ? (&(this->operator*())) : nullptr; }
};
}
I then run it in the following function:
int main()
{
maybe::optional<int> just3(3);
for (int three : just3)
std::cout << three ; //prints "3";
auto adaptor = std::views::transform([](int n) {return n * 2;});
//auto six_range = just3 | adaptor; //Error C2678 binary '|': no operator found which takes a left - hand operand of type 'maybe::optional<int>' (or there is no acceptable conversion)
}
Building in VS2019 with the standard set as /std:c latest, it works fine in the range-based loop but produces the given error when I try to pipe it to an adaptor. Is there something that I need to implement?
CodePudding user response:
The best way to check if your range is a valid C 20 range is to check if your iterator is a valid C 20 iterator:
static_assert(std::forward_iterator<maybe::iterator_to_nowhere<int>>);
And when you do that you'll see that this requirement fails:
/opt/compiler-explorer/gcc-trunk-20221020/include/c /13.0.0/bits/iterator_concepts.h:538:18: note: nested requirement 'same_as<std::iter_reference_t<const _In>, std::iter_reference_t<_Tp> >' is not satisfied
538 | requires same_as<iter_reference_t<const _In>,
| ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
539 | iter_reference_t<_In>>;
| ~~~~~~~~~~~~~~~~~~~~~~
Because you have:
reference operator*() { return *ptr; }
T operator*() const { return *ptr; }
These return different types (reference
is T&
). Returning T&
is correct, but the operator needs to be const
. So:
reference operator*() const { return *ptr; }
Once we fix that, we have a valid input iterator. But for forward iterator, we also need default construction (which is also necessary for the sentinel).
Fixing both of those things, we now have a valid range and the transform
works.