What is needed for this attempt at a range to pipe to a range adaptor?

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?

>Solution :

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.

Leave a Reply