Home > Net >  Do custom iterators always need to explicitly specify value_type?
Do custom iterators always need to explicitly specify value_type?

Time:11-19

Since iterator is deprecated, I began converting iterators in my code base to use non-deprecated constructs. I could not seem to make my indirect iterator compliant with the std::forward_iterator concept unless I explicitly specified value_type. I would like to know if this is expected.

Based on the definition of iter_value_t and indirectly_readible_traits, it seems like there is no automatic inference of std::iter_value_t. Naively, I would have expected std::iter_value_t<Itr> to be defined as std::remove_cvref_t<std::iter_reference_t<Itr>> if no definition for value_type is present (which is checked via has-member-value-type in indirectly_readible_traits).

#include <vector>

template <std::forward_iterator Itr>
class IndirectItr {
public:
    using value_type = std::iter_value_t<Itr>; // **do I need this?**
    
    explicit IndirectItr(Itr itr = {}) : m_itr{itr} {}
    bool operator==(const IndirectItr& rhs) const { return m_itr == rhs.m_itr; }
    bool operator!=(const IndirectItr& rhs) const { return m_itr != rhs.m_itr; }
    typename std::iter_reference_t<Itr> operator *() const { return *m_itr; }
    IndirectItr& operator  () {   m_itr; return *this; }
    IndirectItr operator  (int) { auto ret = *this;   (*this); return ret; }
    typename std::iter_difference_t<Itr> operator-(const IndirectItr& rhs) const { return m_itr - rhs.m_itr; }

private:
    Itr m_itr;
};

using Base = std::vector<int>::iterator;
static_assert(std::forward_iterator<IndirectItr<Base>>);
static_assert(std::same_as<std::iter_value_t<Base>, std::remove_cvref_t<std::iter_reference_t<Base>>>);

P.S. I have several indirect iterator definitions that wrap other iterators. The example above is representative of a custom indirect iterator. I don't have this exact class in my code.

CodePudding user response:

You don't have to have a member value_type on your iterator. But your only alternative is to specialize iterator_traits<T> for your iterator type and provide a value_type alias there. So you may as well make it a member of the iterator.

The value_type cannot be computed from something else, as it may have no obvious relation to reference or any other operation on the iterator. This is one of the things that allows for proxy iterators, which pre-C 20 concepts did not.

CodePudding user response:

std::forward_iterator includes std::input_iterator, which includes std::indirectly_readable, which contains:

requires(const In in) {
  typename std::iter_value_t<In>;
  typename std::iter_reference_t<In>;
  typename std::iter_rvalue_reference_t<In>;
  { *in } -> std::same_as<std::iter_reference_t<In>>;
  { ranges::iter_move(in) } -> std::same_as<std::iter_rvalue_reference_t<In>>;
}

(where In is std::remove_cvref_t<IndirectItr<Base>>).

That typename std::iter_value_t<In>; line requires you to declare a value_type or to specialize std::iterator_traits<IndirectItr<Base>> (and provide value_type there), as explained here.

You cannot specialize std::iterator_traits<IndirectItr<T>> for all T (see also), so you can either pick the first and very reasonable option, or fully specialize for each IndirectItr you intend to use.

  • Related