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."
An iterator can either be a constant or a non-constant/regular 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.
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:
const
access to the referenced data escapes the interfaceThis 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.
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
)
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.
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