Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What to use for `Iterator::pointer` when nothing makes sense?

Tags:

c++

For example, consider some hypothetical to_upper_iterator that iterates over a range of characters, returning std::toupper for operator*. These iterator aliases make sense to me:

template <typename CharT>
struct to_upper_iterator
{
    using value_type = CharT;
    using reference = CharT;
    using difference_type = std::ptrdiff_t;
    using iterator_category = std::random_access_iterator_tag;
};

What doesn't make sense is what should/could be used for the pointer alias. I tried leaving it off, but sure enough I got compilation errors. It seems as though this is due to an addition in C++17. Summarized by en.cppreference.com for the std::iterator_traits type:

If Iterator does not have the five member types difference_type, value_type, pointer, reference, and iterator_category, then this template has no members by any of those names (std::iterator_traits is SFINAE-friendly)

So the question is: for types like this, should I just define pointer to something - my leading favorite being void or void* - or would it make more sense to do something like specialize std::iterator_traits<to_upper_iterator> so that it doesn't contain an alias for pointer.

like image 839
Duncan Avatar asked May 17 '17 08:05

Duncan


3 Answers

The standard c++ models of OutputIterator define all to be void.

example: http://en.cppreference.com/w/cpp/iterator/back_insert_iterator

I would be inclined to do the same for those type that have no meaning in the context of your iterator type.

This iterator of yours looks like it's modelling the concept of an adapter. In which case I might be inclined to start along these lines:

#include <algorithm>
#include <string>
#include <iterator>
#include <utility>
#include <iostream>

template <typename BaseIter>
struct to_upper_iterator
{
    using value_type = typename std::iterator_traits<BaseIter>::value_type;
    using reference = std::add_lvalue_reference_t<std::add_const_t<value_type>>;
    using pointer = std::add_pointer_t<std::add_const_t<value_type>>;
    using difference_type = typename std::iterator_traits<BaseIter>::difference_type;
    using iterator_category = typename std::iterator_traits<BaseIter>::iterator_category;

    to_upper_iterator(BaseIter iter = BaseIter()) : iter_(iter) {}

    value_type operator*() const { return std::toupper(*underlying()); }
    to_upper_iterator& operator++()
    {
        iter_++;
        return *this;
    }

    to_upper_iterator operator++(int)
    {
        auto copy = *this;
        iter_++;
        return copy;
    }

    bool operator!=(const to_upper_iterator& other) const {
        return iter_ != other.iter_;
    }

    // etc. use enable_if to enable functionality depending on iterator_category

private:
    BaseIter& underlying() { return iter_; }
    BaseIter const& underlying() const { return iter_; }

    BaseIter iter_;
};

template<class Iter>
auto make_upper_iterator(Iter iter)
{
    return to_upper_iterator<Iter>(iter);
}

int main()
{
    std::string a = "abcdef";

    auto first = make_upper_iterator(a.begin());
    auto last = make_upper_iterator(a.end());

    std::copy(first, last, std::ostream_iterator<char>(std::cout));
    std::cout << std::endl;

    const char b[] = "abcdef";
    std::copy(make_upper_iterator(std::begin(b)),
        make_upper_iterator(std::end(b) - 1),
        std::ostream_iterator<char>(std::cout));
    std::cout << std::endl;
}
like image 86
Richard Hodges Avatar answered Nov 12 '22 03:11

Richard Hodges


to_upper_iterator::pointer should be a type that can point to the value_type and to mark that you can't modify the value you should make it const, i.e. const CharT*

like image 39
Johan Avatar answered Nov 12 '22 03:11

Johan


Adding my own answer, but assuming SO doesn't force me, won't mark it as the answer. It seems as though the relevant phrasing is in § 27.4.1

27.4.1 Iterator traits [iterator.traits]

To implement algorithms only in terms of iterators, it is often necessary to determine the value and difference types that correspond to a particular iterator type. Accordingly, it is required that if Iterator is the type of an iterator, the types

iterator_traits<Iterator>::difference_type
iterator_traits<Iterator>::value_type
iterator_traits<Iterator>::iterator_category

be defined as the iterator’s difference type, value type and iterator category, respectively. In addition, the types

iterator_traits<Iterator>::reference
iterator_traits<Iterator>::pointer

shall be defined as the iterator’s reference and pointer types, that is, for an iterator object a, the same type as the type of *a and a->, respectively. In the case of an output iterator, the types

iterator_traits<Iterator>::difference_type
iterator_traits<Iterator>::value_type
iterator_traits<Iterator>::reference
iterator_traits<Iterator>::pointer

may be defined as void.

In my case, operator-> doesn't make sense since I'm just returning character types, so I can probably get away with void as @Richard Hodges suggested. I suppose the "correct" answer in the general case is to define some pointer_proxy<T> type (assuming such a thing doesn't already exist in the STL) that holds a T and defines an operator-> that returns T* and use that for the iterator's pointer type

like image 1
Duncan Avatar answered Nov 12 '22 01:11

Duncan