Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a C++11 template that can take a const iterator

In responding to this question on CodeReview, I was thinking about how one might write a template function to indicate const-ness of a contained object.

To be specific, consider this templated function

#include <iostream>
#include <numeric>
#include <vector>

template <class It>
typename std::iterator_traits<It>::value_type average(It begin, It end) {
    typedef typename std::iterator_traits<It>::value_type real;
    real sum = real();
    unsigned count = 0;
    for ( ; begin != end; ++begin, ++count)
        sum += *begin;
    return sum/count;
}

int main()
{
    std::vector<double> v(1000);
    std::iota(v.begin(), v.end(), 42);
    double avg = average(v.cbegin(), v.cend());
    std::cout << "avg = " << avg << '\n';
}

It takes an iterator and calculates an average based on the contained numbers, but it is guaranteed not to modify the vector through the passed iterators. How does one convey this to a user of the template?

Note that declaring it like this:

template <class It>
typename std::iterator_traits<It>::value_type average(const It begin,
    const It end)

doesn't work because it's not the iterator, but the thing the iterator points to, that's const. Do I have to wait for concepts to be standardized?

Note that I don't want to require const iterators, but instead to indicate that they may be safely used here. That is, rather than restricting the caller, I want to convey a promise that my code is making: "I will not modify your underlying data."

like image 509
Edward Avatar asked Jul 10 '14 11:07

Edward


People also ask

Can iterator be const?

An iterator can either be a constant or a non-constant/regular iterator.

What is the difference between const iterator and iterator?

There is no performance difference. A const_iterator is an iterator that points to const value (like a const T* pointer); dereferencing it returns a reference to a constant value ( const T& ) and prevents modification of the referenced value: it enforces const -correctness.


3 Answers

template <class ConstIt>

It's that simple. There's nothing to be enforced on the caller side here, as a non-const iterator is also usable for const access, so it's just API documentation, and that's what your choice of parameter identifier is - API documentation.

That does lead on to the question of enforcement on the callee/function side - so it can't be pretending it will only use the iterator for const access then modify elements anyway. Should you care about that, you could accept the parameter using some identifier making it clear it wasn't meant to be used everywhere throughout the function, then create a const_iterator version with a more convenient identifier. That could be tricky as in general you don't know if the iterator type is a member of a container, let alone what that container type is and whether it has a const_iterator too, so some manner of Concepts would indeed be ideal - fingers crossed for C++14. Meanwhile:

  • have your caller tell you the container type,
  • write your own traits, OR
  • write a simple wrapper class that holds an iterator and ensures only const access to the referenced data escapes the interface

This last wrapper approach is illustrated below (not all of the iterator API is implemented so flesh out as needed):

template <typename Iterator>
class const_iterator
{
  public:
    typedef Iterator                                                 iterator_type;
    typedef typename std::iterator_traits<Iterator>::difference_type difference_type;
    // note: trying to add const to ...:reference or ..:pointer doesn't work,
    //       as it's like saying T* const rather than T const* aka const T*.
    typedef const typename std::iterator_traits<Iterator>::value_type& reference;
    typedef const typename std::iterator_traits<Iterator>::value_type* pointer;

    const_iterator(const Iterator& i) : i_(i) { }
    reference operator*() const { return *i_; }
    pointer operator->() const { return i_; }    
    bool operator==(const const_iterator& rhs) const { return i_ == rhs.i_; }
    bool operator!=(const const_iterator& rhs) const { return i_ != rhs.i_; }    
    const_iterator& operator++() { ++i_; return *this; }
    const_iterator operator++(int) const { Iterator i = i_; ++i_; return i; }
  private:
    Iterator i_;
};

Sample usage:

template <typename Const_Iterator>
void f(const Const_Iterator& b__, const Const_Iterator& e__)
{
    const_iterator<Const_Iterator> b{b__}, e{e__}; // make a really-const iterator
    // *b = 2;  // if uncommented, compile-time error....
    for ( ; b != e; ++b)
        std::cout << *b << '\n';
}

See it running at ideone.com here.

like image 108
Tony Delroy Avatar answered Oct 04 '22 14:10

Tony Delroy


You may add some traits to see if the iterator is a const_iterator:

template <typename IT>
using is_const_iterator = 
        std::is_const<typename std::remove_reference<typename std::iterator_traits<IT>::reference>::type>;

And then use something like:

template <typename IT>
typename
std::enable_if<is_const_iterator<IT>::value,
               typename std::iterator_traits<It>::value_type
>::type average(It begin, It end);

But this will avoid the use of iterator which are convertible to const_iterator. So it will be better to restrict iterator when const is forbidden (as in std::sort)

like image 28
Jarod42 Avatar answered Oct 04 '22 15:10

Jarod42


It takes an iterator and calculates an average based on the contained numbers, but it is guaranteed not to modify the vector through the passed iterators. How does one convey this to a user of the template?

You could use SFINAE to disable the template when non-const iterators are passed, but that would be an unnecessary limitation.


Another way is to accept ranges instead of iterators. This way you could write:

template <class Range>
typename Range::value_type average(Range const& range);

The user can pass a container or iterator range in there.

like image 31
Maxim Egorushkin Avatar answered Oct 04 '22 15:10

Maxim Egorushkin