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 typesdifference_type
,value_type
,pointer
,reference
, anditerator_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
.
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;
}
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*
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 typesiterator_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
anda->
, respectively. In the case of an output iterator, the typesiterator_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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With