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

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".

Related

reference with const access

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.

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.

How to to pass iterators to the std::lower_bound() comparison function?

In the following declaration borrowed from cplusplus.com
template<class ForwardIterator, class T, class Compare>
ForwardIterator lower_bound(ForwardIterator first, ForwardIterator last, const T& val, Compare comp);
comp() should resemble something like this:
template<class T>
bool comp(const T& v1, const T& v2);
The problem is that I don't want to pass the value type there. I want to pass iterators to it and then shift them okay, just stare at them silently inside comp() before dereferencing. (Not to mention - log them.) Is there any workaround for this?
Of course, I can write my own container class with its own iterators, and of course I can write my own implementation of std::lower_bound(). Both options are rather unpleasant.
From the std::lower_bound doc, one can read for bool comp(const Type1 &a, const Type2 &b);:
The type Type1 must be such that an object of type ForwardIt can be dereferenced and then implicitly converted to Type1. The type Type2 must be such that an object of type T can be implicitly converted to Type2. ​
This means, std::lower_bound will always call comp with an element from the range as the left hand side argument, and value as the right hand side argument. If your search range is a contiguous range (meaning you're dealing with std::vector, std::array, std::valarray, std::string, ..., or C-style arrays), you can devise an iterator from the distance between the range's start and comp's left hand side argument:
auto v = std::vector<int>{0, 1, 2, 3, 4, 5};
auto comp = [&v](const int &lhs, const int &rhs)
{
auto it_lhs = cbegin(v) + std::distance(std::addressof(*cbegin(v)), &lhs);
return *it_lhs < rhs;
};
std::cout << *std::lower_bound(begin(v), end(v), 2, comp) << "\n";
From the doc:
The signature of the predicate function should be equivalent to the
following:
bool pred(const Type1 &a, const Type2 &b);
The signature does not need to have const &, but the function must not
modify the objects passed to it.
So you can not, and should not, do this. std::lower_bound has specific purpose and it should not modify the input in any way. Write your own function for this purpose.
If you need indices only, and your container stores its elements in linear, continuous block of memory, you could do this (example with std::vector):
std::vector<...> vec;
...
const auto* firstElemPtr = &vec[0];
std::lower_bound(vec.begin(), vec.end(), key, [firstElemPtr](const auto& left, const auto& right) -> bool {
size_t index = &left - firstElemPtr;
// now do the comparison
});

Can I rely on initializer_list::const_iterator being a plain pointer?

This question indicates that std::initializer_list<int>::const_iterator type is just a plain int const* pointer, but the wording of the standard as cited in this answer (to the same question) sounds more like a suggestion than like a guarantee to me.
In my code, the problem occurs as follows: I have a function processing a sub-range of an initializer_list, and in a different situation I reuse the code from a convenience function passing a single element (as a single-element range):
void doStuff(int const* begin, int const* end)
{ ... do stuff ... }
// main use; in the real code, 'init' is a parameter of a function
std::initializer_list<int> init({1, 2, 3, 4, 5});
doStuff(init.begin() + 1, init.end());
// alternative use of doStuff, relies on the pointer assumption
int x = 6;
doStuff(&x, (&x) + 1);
}
This construction relies on the fact that iterators are indeed pointers. It works at least with my clang++ 3.9 compiler. Can I rely on this to always work, i.e., is the pointer assumption portable? guaranteed to be portable? Or should I rather change the argument type of doStuff into a template parameter, to be on the safe side?
template <typename Iterator>
void doStuff(Iterator begin, Iterator end)
{ ... do stuff ... }
Yes, you can rely on it, 18.9 from C++14 gives us inside class initializer_list the definitions:
typedef const E* iterator;
typedef const E* const_iterator;
Other types in C++ are different -- for example std::vector, where the iterator is implementation defined, and some implementations use a raw pointer as an iterator, and some use a class.
Section 18.9 of the standard gives a synopsis of the <initializer_list> header as follows:
namespace std {
template<class E> class initializer_list {
public:
typedef E value_type;
typedef const E& reference;
typedef const E& const_reference;
typedef size_t size_type;
typedef const E* iterator;
typedef const E* const_iterator;
constexpr initializer_list() noexcept;
constexpr size_t size() const noexcept; // number of elements
constexpr const E* begin() const noexcept; // first element
constexpr const E* end() const noexcept; // one past the last element
};
// 18.9.3 initializer list range access
template<class E> constexpr const E* begin(initializer_list<E> il) noexcept;
template<class E> constexpr const E* end(initializer_list<E> il) noexcept;
}
Furthermore, the begin() and end() functions on an initializer_list are guaranteed to return a const E*, as we can see from 18.9.2.
So yes, you can rely on it.

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.