0

Suppose I have a templated function which has a parameter used as an iterator (albeit without any concepts/requirements - C++17 or earlier), named my_iter.

Can I generically ensure that iterator is a const iterator, or get a const iterator to the same position?

Note: Unlike in this question, which concerns an iterator vs a const_iterator of some assumed-known container class, here we don't know what that container class is, if it exists at all. Thus I don't want to write something like:

auto it = std::cbegin(my_container);
auto desired_iter = it + distance(it, my_iter);

as suggested in this method, nor even:

auto desired_iter = ContainerType::const_iterator(my_iter);
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    Checking for constness is easy (check if `decltype(*iter)` or `std::iterator_traits<...>::reference` are const references). As for making the iterator immutable, I'd write a wrapper that inherits from the iterator and overrides `*` and `->`. – HolyBlackCat Apr 18 '20 at 10:23
  • @HolyBlackCat: But won't it need to use the underlying iterator's `*` and `->`, which are non-const? – einpoklum Apr 18 '20 at 11:08
  • Yes, but wrapping them in custom overloaded `*` and `->` lets you add constness. – HolyBlackCat Apr 18 '20 at 11:20
  • Please ask one question per question. Regarding the one about getting const_iterator, you can see this [C++ iterator to const_iterator](https://stackoverflow.com/q/7759246/10147399) – Aykhan Hagverdili Apr 18 '20 at 11:57
  • @Ayxan: That question, or at least the accepted answer, regards the case of a known container, when you can easily determine the appropriate container type. – einpoklum Apr 18 '20 at 12:12
  • @einpoklum regardless of the answer, it's an exact duplicate question. Perhaps start a bounty on that question and specify your exact requirements? – Aykhan Hagverdili Apr 18 '20 at 12:48
  • @Ayxan: Actually, that's not the case, because "a `const_iterator`" is something that's defined exclusively within containers. What I will do is edit both questions to clarify the difference. – einpoklum Apr 18 '20 at 13:48

1 Answers1

1

You could create a wrapper ConstIterator class to ensure const-correctness:

#include <list>
#include <iostream>
#include <string>

template <typename Iterator>
class ConstIterator {
public:
        ConstIterator(Iterator const it) /* Not explicit */
                : it_{ it }
        {}

        auto& operator++() noexcept
        {
                ++it_;
                return *this;
        }

        [[nodiscard]] auto operator++(int) noexcept
        {
                auto copy = *this;
                ++it_;
                return copy;
        }

        auto& operator--() noexcept
        {
                --it_;
                return *this;
        }

        [[nodiscard]] auto operator--(int) noexcept
        {
                auto copy = *this;
                --it_;
                return copy;
        }

        [[nodiscard]] auto const& operator*() const noexcept
        {
                return *it_;
        }

        auto const* operator->() const noexcept
        {
                return &*it_; // const pointer
        }

        auto& operator+= (std::size_t const step) noexcept
        {
                it_ += step;
                return *this;
        }

        auto& operator-= (std::size_t const step) noexcept
        {
                it_ -= step;
                return *this;
        }

        auto operator+ (std::size_t const step) noexcept
        {
                auto copy = *this;
                return copy += step;
        }

        auto operator- (std::size_t const step) noexcept
        {
                auto copy = *this;
                return copy -= step;
        }

        auto operator- (ConstIterator const rhs) noexcept
        {
                return it_ - rhs.it_;
        }


        auto operator == (ConstIterator const rhs) noexcept
        {
                return it_ == rhs.it_;
        }

        auto operator != (ConstIterator const rhs) noexcept
        {
                return !(*this == rhs);
        }

private:
        Iterator it_;
};


// test it
template<typename Iter>
void print(Iter beg, Iter end) noexcept
{

        ConstIterator<Iter> cbeg{ beg }; // const iterator

        for (auto it = cbeg; it != end; ++it) {
                std::cout << *it << ' '; // accessing works fine
        }
        std::cout << '\n';


        // cbeg->clear(); // won't compile
        // *cbeg = decltype(*cbeg){}; // won't compile
}


int main()
{
        std::list<std::string> list{"1", "2", "3"};
        print(list.begin(), list.end());
}
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • And what's my guarantee that the underlying iterator doesn't alter anything, say, on advancing? – einpoklum Apr 18 '20 at 17:00
  • That's a good point. All I can say is that iterators *usually* don't do such nasty things. What would you expect the const version of the iterator to do in that case? I always imagine iterators as more abstracted pointers. Are you familiar with any container/iterator type that alter underlying data on advancing normal iterators but does something different on advancing const iterators? – Aykhan Hagverdili Apr 18 '20 at 17:14
  • 1. I want the compilation to fail, actually. 2. Oh, sure. Suppose your iterator advances over cached memory/storage: A non-const iterator could, on advancing, trigger a preloading of the cache with the element not yet read; a const iterator would be lazy and not do anything until an actual read. But - it could also be the case that there _is_ no const iterator equivalent of the non-const iterator I've gotten, and I want to know that. – einpoklum Apr 18 '20 at 19:03
  • What you are asking is so generic that I don't think such a thing is even possible in the general case. After all, an `iterator` and a `const_iterator` instances are not related in any way in the eyes of the type system. Any API could decide what "constant iterator" even means for them. For now, this answer is the best I could come up with. If you find something better, I'd be interested to know. – Aykhan Hagverdili Apr 18 '20 at 19:13
  • I wonder, then, how come this is not doable via [`std::iterator_traits`](https://en.cppreference.com/w/cpp/iterator/iterator_traits); which is what I would expect personally. – einpoklum Apr 18 '20 at 19:42
  • Your class doesn't have the necessary member typedefs, so formally it's not a valid iterator. Honestly, I'd just inherit from the original iterator, then you only need to change `*`, `->`, and some of the typedefs. – HolyBlackCat Apr 19 '20 at 00:33
  • @HolyBlackCat I did think about inheritance, but then I didn't do it because of all the nasty things like slicing inheritance may entail. – Aykhan Hagverdili Apr 19 '20 at 06:12