c++iteratorc++20constexprstatic-assert

Testing if an input iterator can write


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...

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");

Solution

  • 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.