Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading