reference with const access - c++

Using pointers, I can create all combinations of (const/non_const) to (T / const T) for any type T, as discussed in detail this answer.
But using references, how can I define a reference to a variable which can be dereferences (like an iterator), such that the dereferenced reference gives a const access?
For example, consider this program:
#include <array>
int main()
{
std::array<int, 3> a{0, 0, 0};
auto const& iter = a.begin();
// iter++; // Error!
(*iter)++;
}
iter is const variable, and if you uncomment the line iter++ the code cannot compile.
But iter dereferences to a non-const int, which in fact can be incremented with (*iter)++. In a sense, iter mimics int * const, i.e., a const pointer to int.
Is it possible to instead have a (const or non_const) reference iter, such that *iter is a const int?
Please bear in mind that I am aware that I could use const auto& iter = a.cbegin() in the example above. The purpose of the question is how to define iter, such that, when dereferenced, give a const access, irrespective of what is on the right of the equal operator.
For iterators I can imagine to explicitly bind to a const_iterator, like:
const decltype(a)::const_iterator& iter = a.begin();
This solution works because I know that the equivalent "const-access" of an iterator is a const_iterator.
But, more generally:
(some type)& iter = rhs
If all I know is that *rhs is a int, is it possible to find out the type of iter, such that *iter is a const int?
Another point worth to consider is the possible cost of the conversion of rhs.

Is it possible to instead have a (const or non-const) reference iter, such that *iter is a const int?
You call the std::array::cbegin() member function that will return a std::array<int, 3>::const_iterator. Dereferencing that will give you a const int&.
You can however not take the const_iterator by a non-const reference. You must take it by value or by const& (to prolong the life of the temporary returned by the cbegin() function). The general recommendation is to take iterators by value though.
is it possible to find out the type of iter, such that *iter is a const int?
If you know that iter is some kind of pointer (const T*, const T*&, T* or T*& etc.) then you could get a const T* like this:
const std::remove_cvref_t<decltype(iter)> constiter = iter;
For the general case, getting a const_iterator from an iterator without using the container's typedef for const_iterator, will be a bit cumbersome. One way could be to wrap the iterator in an interator class that only returns const&s when dereferenced.
Here's an example with a std::list<int>::iterator which is a little more complicated than a pointer. It works for pointers too though.
#include <iostream>
#include <list>
template<class It>
struct const_iterator {
using IterType = std::remove_reference_t<It>;
using Traits = std::iterator_traits<IterType>;
using difference_type = typename Traits::difference_type;
using value_type = const typename Traits::value_type;
// ^^^^^
using pointer = value_type*;
using reference = value_type&;
using iterator_category = typename Traits::iterator_category;
const_iterator(It nc) : value(nc) {}
// dereferencing gives a const&
reference operator*() const { return *value; }
bool operator==(const const_iterator& rhs) const { return value == rhs.value; }
bool operator!=(const const_iterator& rhs) const { return !(*this == rhs); }
const_iterator& operator++() { ++value; return *this; }
const_iterator operator++(int) { const_iterator rv(*this); ++value; return rv; }
// Add conditional support for proxy member functions supported
// by the original iterator type / iterator_category
IterType value;
};
template<class It>
const_iterator(It) -> const_iterator<It>;
int main()
{
std::list<int> a{1, 2, 3};
auto iter = a.begin();
*iter = 10; // Ok
const_iterator cit(iter); // make a const_iterator from the original iterator
++cit; // Ok
std::cout << *cit << '\n'; // prints 2
//*cit = 20; // Error: *cit is a `const int&`
}

The purpose of the question is how to define iter, such that, when dereferenced, give a const access
And this is where const iterators come into play. For iterators it's actually a less global question, because a const iterator merely means a standard iterator to a constant container:
const std::array<int, 3> a{ 0, 0, 0 };
static_assert(
std::is_same_v<decltype(a.begin()), decltype(a.cbegin())>,
"The iterators are of different type"
);
Constness of the iterators' own types in this case doesn't actually depend on whether they point to a const type or not. As you already noticed, const iterator instead means that the iterator itself cannot be changed and point to a different object.
Answering your second question:
Is it possible to instead have a (const or non_const) reference to const int?
No, a reference is always const implicitly and cannot be rebound after being bound to a variable.

Related

Is the const overload of begin/end of the range adapters underconstrained?

In C++20, some ranges have both const and non-const begin()/end(), while others only have non-const begin()/end().
In order to enable the range adapters that wraps the former to be able to use begin()/end() when it is const qualified, some range adapters such as elements_view, reverse_view and common_view all provide constrained const-qualified begin()/end() functions, for example:
template<view V>
requires (!common_­range<V> && copyable<iterator_t<V>>)
class common_view : public view_interface<common_view<V>> {
public:
constexpr auto begin();
constexpr auto end();
constexpr auto begin() const requires range<const V>;
constexpr auto end() const requires range<const V>;
};
template<input_­range V, size_t N>
requires view<V> && has-tuple-element<range_value_t<V>, N>
class elements_view : public view_interface<elements_view<V, N>> {
public:
constexpr auto begin();
constexpr auto end();
constexpr auto begin() const requires range<const V>;
constexpr auto end() const requires range<const V>;
};
The begin() const/end() const will only be instantiated when const V satisfies the range, which seems reasonable.
But only one simple constraint seems to be insufficient. Take common_view for example, it requires that V is not a common_range. But when V is not a common_range and const V is a common_range (I know that such a range is extremely weird, but in theory, it can exist, godbolt):
#include <ranges>
struct weird_range : std::ranges::view_base {
int* begin();
const int* end();
std::common_iterator<int*, const int*> begin() const;
std::common_iterator<int*, const int*> end() const;
};
int main() {
weird_range r;
auto cr = r | std::views::common;
static_assert(std::ranges::forward_range<decltype(cr)>); // ok
cr.front(); // ill-formed
}
In the above example, const V still satisfied the range concept, and when we apply r to views::common, its front() function will be ill-formed.
The reason is that view_interface::front() const will still be instantiated, and the common_iterator will be constructed inside the begin() const of common_view, this will cause a hard error to abort the compilation since const V itself is common_range.
Similarly, we can also create a weird range based on the same concept to make the front() of views::reverse and views::keys fail (godbolt):
#include <ranges>
struct my_range : std::ranges::view_base {
std::pair<int, int>* begin();
std::pair<int, int>* end();
std::common_iterator<int*, const int*> begin() const;
std::common_iterator<int*, const int*> end() const;
};
int main() {
my_range r;
auto r1 = r | std::views::reverse;
static_assert(std::ranges::random_access_range<decltype(r1)>); // ok
r1.front(); // ill-formed
auto r2 = r | std::views::keys;
static_assert(std::ranges::random_access_range<decltype(r2)>); // ok
r2.front(); // ill-formed
}
So, is the const overload of begin()/end() of the range adapters underconstrained, or is the definition of weird_range itself ill-formed? Can this be considered a standard defect?
This question is mainly inspired by the LWG 3592, which states that for lazy_split_view, we need to consider the case where the const Pattern is not range, and then I subsequently submitted the LWG 3599. When I further reviewing the begin() const of other range adapters, I found that most of them only require const V to be range, this seemingly loose constraint made me raise this question.
In order to enable the range adapters' begin() const, theoretically, the constraints for const V should be exactly the same as V, which means that the long list of constraints on V, such as elements_view, needs to be replaced with const V instead of only constraints const V to be a range.
But in fact, it seems that the standard is not interested in the situation where the iterator and sentinel types of V and const V are very different.
Recent SG9 discussion on LWG3564 concluded that the intended design is that x and as_const(x) should be required to be substitutable with equal results in equality-preserving expressions for which both are valid. In other words, they should be "equal" in the [concepts.equality] "same platonic value" sense. Thus, for instance, it is not valid for x and as_const(x) to have entirely different elements.
The exact wording and scope of the rule will have to await a paper, and we'll have to take care to avoid banning reasonable code. But certainly things like "x is a range of pairs but as_const(x) is a range of ints" are not within any reasonable definition of "reasonable".

Container begin / end / cbegin / cend semantics, iterator / const_iterator compatibility

I've been working on a custom ReversibleContainer, and I thought I was on the right track, but I've hit a snag in testing while going through the semantics of the Container named requirements which makes me think I've fundamentally mis-implemented this. I'm working with C++17.
In particular, my current implementation is formed somewhat like this (pardon errors, I'm condensing it to an example as I type here), where:
Item is the type the container holds
element is the type iterators dereference to (it's convertible to Item)
struct is used for overall brevity in this snippet
only the types and members that I think are relevant are included
struct my_container {
using value_type = Item;
using reference = value_type &;
using const_reference = const value_type &;
using size_type = std::vector<Item>::size_type;
using difference_type = std::vector<Item>::difference_type;
struct element {
// ...
};
// V is value type, D is part of forward/reverse iterator control
template <typename V, int D> struct iterator_ {
using iterator_category = std::random_access_iterator_tag;
using value_type = V;
using reference = V &;
using pointer = V *;
using difference_type = my_container::difference_type;
iterator_ (); // custom
iterator_ (const iterator_<V,D> &) = default;
iterator_ (iterator_<V,D> &&) = default;
~iterator_ () = default;
iterator_<V,D> & operator = (const iterator_<V,D> &) = default;
iterator_<V,D> & operator = (iterator_<V,D> &&) = default;
bool operator == (const iterator_<V,D> &) const;
// ...
};
using iterator = iterator_<element, 1>;
using const_iterator = iterator_<const element, 1>;
using reverse_iterator = iterator_<element, -1>;
using const_reverse_iterator = iterator_<const element, -1>;
iterator begin ();
iterator end ();
const_iterator cbegin () const;
const_iterator cend () const;
reverse_iterator rbegin ();
reverse_iterator rend ();
const_reverse_iterator crbegin () const;
const_reverse_iterator crend () const;
};
Now, I'm looking at the operational semantics of begin, end, cbegin and cend (where a is a my_container, and C is its type):
expression
return type
semantics
a.begin()
(const_)iterator
iterator to the first element of a
a.end()
(const_)iterator
iterator to one past the last element of a
a.cbegin()
const_iterator
const_cast<const C&>(a).begin()
a.cend()
const_iterator
const_cast<const C&>(a).end()
And the problem with my current implementation is that this expression, derived from the cbegin (and likewise cend), is invalid:
a.cbegin() == const_cast<const my_container&>(a).begin()
Because my iterator and const_iterator types are incompatible due to the const being wrapped up in the iterator type via the template parameters to iterator_, and also because my begin() is not const. And now I'm getting that sinking feeling that I have a fundamental flaw in my implementation.
The second problem with my current implementation is that the requirements list the return type of begin and end as "(const_)iterator", and I am only just noticing the "(const_)" now. However, my begin and end do not return a const_iterator.
My conclusion, then, is that my implementation does not meet the operational semantics requirements of Container, and is therefore invalid in its current form. And now I'm sad. :(
So, I'm confused about:
General compatibility requirements of iterator and const_iterator.
The cv-qualifiers on the declaration of begin() and end().
And my questions are:
Am I correct in my conclusion that my container currently fails to meet the requirements of Container wrt. begin, end, cbegin, and cend?
Do the iterator and const_iterator types need to be equality comparable with each other?
Does const_iterator need to be copy constructible and assignable from an iterator?
Do begin() and end() have to be declared as const?
Did I make a mistake in wrapping up the const in iterator_::value_type?
What does "(const_)iterator" mean for the return type of begin and end?
I realize that looks like a lot of questions but they all sort of boil down to the single question of what the requirements for interoperability between iterator and const_iterator are. I hope this post makes sense.
iterator begin ();
iterator end ();
const_iterator begin () const;
const_iterator end () const;
const_iterator cbegin () const;
const_iterator cend () const;
And yes, const_iterator it = iterator; should work (but not the other way around), as should == (I am not certain the first is mandated, but you should still do it).
Also consider writing SCARY iterators, where the iterator is not a subtype of the container.
template<class T>
struct foo {
template<class U, std::enable_if_t<std::is_same_v<std::remove_cv_t<U>, std::remove_cv_t<T>>,bool> =true>
friend bool operator==( foo const& lhs, foo<U> const& rhs );
};
This is an example of a == that works between types.

Bidirectional iterator implementation

By some reasons I need to implement a bidirectional iterator, after some time, I got this result (add parameter tells what the side the iterator should move to (to avoid code duplication, when implementing reverse_iterator)):
#include <iterator>
namespace gph {
template <typename T, int add> class BidirectionalIterator;
template<typename T, int add>
void swap(BidirectionalIterator<T, add>& it1, BidirectionalIterator<T, add>& it2) {
it1.swap(it2);
}
template<typename T, int add>
class BidirectionalIterator {
private:
T *currentPosition, *begin, *end;
public:
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator_category = std::bidirectional_iterator_tag;
inline BidirectionalIterator(T* currentPosition, T* begin, T* end):currentPosition(currentPosition), begin(begin), end(end) {}
//copy constructor
inline BidirectionalIterator(const BidirectionalIterator& iterator)
:BidirectionalIterator(iterator.currentPosition, iterator.begin, iterator.end) {}
//move constructor
inline BidirectionalIterator(BidirectionalIterator&& iterator) noexcept
:BidirectionalIterator(iterator.currentPosition, iterator.begin, iterator.end) {}
//copy and move assignment statement
inline BidirectionalIterator& operator=(BidirectionalIterator iterator) {
gph::swap(*this, iterator);
}
inline void swap(BidirectionalIterator& iterator) {
std::swap(currentPosition, iterator.currentPosition);
std::swap(begin, iterator.begin);
std::swap(end, iterator.end);
}
inline reference operator*() const {
return *currentPosition; //dangerous if the iterator is in not-dereferenceable state
}
inline BidirectionalIterator& operator++() {
if (currentPosition != end) currentPosition += add;
return *this;
}
inline bool operator==(const BidirectionalIterator& iterator) const {
return currentPosition == iterator.currentPosition;
}
inline bool operator!=(const BidirectionalIterator& iterator) const {
return !(*this == iterator);
}
inline BidirectionalIterator operator++(int) {
BidirectionalIterator past = *this;
++*this;
return past;
}
inline BidirectionalIterator& operator--() {
if (currentPosition != begin) currentPosition -= add;
return *this;
}
inline BidirectionalIterator operator--(int) {
BidirectionalIterator past = *this;
--*this;
return past;
}
};
}
I've tried to satisfy MoveAssignable, MoveConstructible, CopyAssignable, CopyConstructible, Swappable, EqualityComparable, LegacyIterator, LegacyInputIterator, LegacyForwardIterator, LegacyBidirectionalIterator named requirements.
Some of their requirements are expressed in operators overloading, but some ones from them I do not know how to implement (perhaps, they are automatically implemented by other ones?), for instance: i->m or *i++ (from here). First question: how should I implement them?
Second question: is my iterator implementation good? What are its drawbacks, where did I make mistakes?
P.S. The questions are on edge of unconstructiveness, but I really need help with it. Sorry for my english.
I find it hard to find a definitive answer to this, so just some thoughts, which may be uncomplete and are open to discussion.
i->m can be implemented by inline pointer operator->() { return this->currentPosition; }
*i++ should already be covered by your implementation
I don't see any reason to swap all the pointers in operator=. For three reasons:
You are swapping the values with a local variable
The move constructor doesn't swap any values (would be inconsistent behaviour between BidirectionalIterator newIt=oldIt; and BidirectionalIterator newIt(oldIt);, but it actually isn't because of the previous point)
Those pointers are not unique resources, so there is no problem in copying them and sharing them between multiple instances
operator= is missing a return.
You have using difference_type = std::ptrdiff_t; but don't implement operator- which would return difference_type, why not implement it?
Reverse iterators can be implemented easier by std::reverse_iterator which will just wrap your iterator and invert ++ and -- and so on.
You probably want to find an easy way to implement a const version of the iterator (a version that always returns a const T& or const T*). I saw three versions of this:
duplicating all the code
using const cast
using an additional template parameter bool TIsConst and using pointer = std::conditional_t<TIsConst, const T*, T*>;
using a templated iterator with parameter const T on the other hand might appear easy, but fails to satisfy a requirement, see this question

When is it sufficient to declare const_iterator as a const iterator?

For example, I could define a container in a way similar to:
template <typename T>
class Example
{
public:
using value_type = T;
using iterator = value_type*;
using const_iterator = const iterator;
//etc
};
However, is it okay to do this with a user-defined iterator?
template<typename T>
class Example
{
public:
using value_type = T;
/*friend?*/ class iterator;
using const_iterator = const iterator; //is this okay?
//etc
};
A detailed explanation would be appreciated.
A const_iterator and a const iterator represent two different things. In each case, the const applies to something different: the object "pointed to" by the iterator in the first case, and the iterator itself in the second. So, it is never "sufficient" to use one for the other.
In pointer speak (and pointers are iterators), this would be the difference between "pointer to const" and "const pointer".
const int* it; // pointer to const int: a const_iterator
int* it const; // const pointer to int: a const iterator
You can even have const iterators to const, or const const_iterator:
const int* it const;

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."
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.
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.
You could try always dereferencing the iterator through some function deref().
template <typename It>
typename ::std::remove_reference<typename ::std::iterator_traits<It>::reference>::type const&
deref(It it)
{
return *it;
}
Which would guarantee the underlying value will not be modified.