[range] Should ranges really propagate constess to the data ?

Hello everyone, Here is an example code that motivates my question : typedef std::vector<int> vector_t; boost::sub_range<vector_t> three_first(vector_t& _v) {return boost::make_iterator_range(_v.begin(), _v.begin() + 3); } int main(int argc, char* argv[]) { vector_t v; boost::fill(three_first(v), 1); // Fail : you cannot assign to a variable that is const return 0; } Taken from Boost.GIL documentation, the definition of image views is : An image view is a generalization of STL's range concept to multiple dimensions. Similar to ranges (and iterators), image views are shallow, don't own the underlying data and don't propagate their constness over the data. For example, a constant image view cannot be resized, but may allow modifying the pixels. For pixel-immutable operations, use constant-value image view (also called non-mutable image view). I expected the range concept to follow these lines regarding constness, but I'm not very sure anymore... for instance sub_range have : iterator begin() { return base::begin(); } *const_iterator* begin() const { return base::begin(); } and boost::begin() is : inline range_iterator<T>::type begin( T& r ) {...} inline range_iterator<*const* T>::type begin( const T& r ) {...} which means that I'm probably wrong. Am I missing something ? Regards, Samuel

On 22/07/2011 16:31, Samuel Debionne wrote:
for instance sub_range have :
iterator begin() { return base::begin(); } *const_iterator* begin() const { return base::begin(); }
and boost::begin() is :
inline range_iterator<T>::type begin( T& r ) {...} inline range_iterator<*const* T>::type begin( const T& r ) {...}
which means that I'm probably wrong. Am I missing something ?
That explains the strange problems I had when I tried using sub_range. iterator_range does not have those problems. But it seems to be a feature, it you look at the doc of sub_range it says, when comparing sub_range to iterator_range, "Moreover, the sub_range class can propagate constness since it knows what a corresponding const_iterator is." This is, in my opinion, a mistake. const sub_range<T> should *not* behave like sub_range<const T>.

On Fri, Jul 22, 2011 at 6:16 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
That explains the strange problems I had when I tried using sub_range. iterator_range does not have those problems.
But it seems to be a feature, it you look at the doc of sub_range it says, when comparing sub_range to iterator_range, "Moreover, the sub_range class can propagate constness since it knows what a corresponding const_iterator is."
This is, in my opinion, a mistake. const sub_range<T> should *not* behave like sub_range<const T>.
Why (not)? What's the problem with it? Olaf

On 07/23/2011 02:21 PM, Olaf van der Spek wrote:
On Fri, Jul 22, 2011 at 6:16 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
That explains the strange problems I had when I tried using sub_range. iterator_range does not have those problems.
But it seems to be a feature, it you look at the doc of sub_range it says, when comparing sub_range to iterator_range, "Moreover, the sub_range class can propagate constness since it knows what a corresponding const_iterator is."
This is, in my opinion, a mistake. const sub_range<T> should *not* behave like sub_range<const T>.
Why (not)? What's the problem with it?
Whether the sub_range object is a temporary or not should not affect whether the wrapped object is writable or not.

Den 23-07-2011 15:16, Mathias Gaunard skrev:
Whether the sub_range object is a temporary or not should not affect whether the wrapped object is writable or not.
You can always use iterator_range or std::make_pair() in this case. The problem also goes away with move-semantics. -Thorsten

on Fri Jul 22 2011, Samuel Debionne <debionne-AT-hydrowide.com> wrote:
Hello everyone, Here is an example code that motivates my question :
<snip> Well, if we want something like std::vector to be a Range, then we have to accept that at least *some* Ranges do propagate constness. And I'm pretty sure that we don't want other Ranges (std::pair<char*,char*>) to propagate constness. So unless the documentation says otherwise, I would assume that the concept doesn't say anything about whether constness is propagated. If you want to write generic Range code you have to account for both cases. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On 07/23/2011 05:13 PM, Dave Abrahams wrote:
on Fri Jul 22 2011, Samuel Debionne<debionne-AT-hydrowide.com> wrote:
Hello everyone, Here is an example code that motivates my question :
<snip>
Well, if we want something like std::vector to be a Range, then we have to accept that at least *some* Ranges do propagate constness. And I'm pretty sure that we don't want other Ranges (std::pair<char*,char*>) to propagate constness. So unless the documentation says otherwise, I would assume that the concept doesn't say anything about whether constness is propagated. If you want to write generic Range code you have to account for both cases.
Constness is propagated for containers, yes, but sub_range is not a container, it is a view of another range, so it should propagate the constness.

On Sat, Jul 23, 2011 at 10:13 AM, Mathias Gaunard < mathias.gaunard@ens-lyon.org> wrote:
On 07/23/2011 05:13 PM, Dave Abrahams wrote:
on Fri Jul 22 2011, Samuel Debionne<debionne-AT-**hydrowide.com> wrote:
Hello everyone,
Here is an example code that motivates my question :
<snip>
Well, if we want something like std::vector to be a Range, then we have to accept that at least *some* Ranges do propagate constness. And I'm pretty sure that we don't want other Ranges (std::pair<char*,char*>) to propagate constness. So unless the documentation says otherwise, I would assume that the concept doesn't say anything about whether constness is propagated. If you want to write generic Range code you have to account for both cases.
Constness is propagated for containers, yes, but sub_range is not a container, it is a view of another range, so it should propagate the constness.
[Just to avoid confusion.] ...so it should *not* propagate the constness. Yes, I agree, but that was explicitly the interface decision and luckily there's usually a workaround (use boost::iterator_range). - Jeff

<snip>
Well, if we want something like std::vector to be a Range, then we have to accept that at least *some* Ranges do propagate constness. And I'm pretty sure that we don't want other Ranges (std::pair<char*,char*>) to propagate constness. So unless the documentation says otherwise, I would assume that the concept doesn't say anything about whether constness is propagated. If you want to write generic Range code you have to account for both cases.
Constness is propagated for containers, yes, but sub_range is not a container, it is a view of another range, so it should propagate the constness.
[Just to avoid confusion.]
...so it should *not* propagate the constness. Yes, I agree, but that was explicitly the interface decision and luckily there's usually a workaround (use boost::iterator_range).
I am currently considering the possible changes we could make to sub_range. Ultimately sub_range is implemented as the original author intended. If one specified the range using a template parameter that is a Range type one must determine which of the mutable of const iterator types to use in the underlying iterator_range. Fundamentally the iterator_range class makes you decide by providing the iterator type, whereas the sub_range allows you to specify the Range type and then provides multiple overloaded implementations of begin/end that extract the appropriate types via the range_iterator meta-function. How do you imagine the const / mutable underlying iterator would be chosen? If you are proposing that the const-ness of the sub_range template parameter is used, I think that this would break to much existing code. The current semantics have been in place since at least version 1.33. I do agree that sub_range's behaviour is rarely required or desirable. My approach has been to use iterator_range for most of my code. I imagine that you would prefer a class similar to iterator_range differing in so much as it takes a Range or const Range as the template parameter. I'll give this some more thought. I would like to avoid the proliferation of similar classes, and breaking interface changes, but I am motivated to provide a solution for your use case.
- Jeff
Regards, Neil Groves

On 07/24/2011 03:41 PM, Neil Groves wrote:
How do you imagine the const / mutable underlying iterator would be chosen? If you are proposing that the const-ness of the sub_range template parameter is used, I think that this would break to much existing code. The current semantics have been in place since at least version 1.33.
I doubt it would break much code if something that was a const_iterator previously is now an iterator, since an iterator is usually convertible to its const version.

On Sun, Jul 24, 2011 at 6:41 AM, Neil Groves <neil@grovescomputing.com>wrote: [...]
I am currently considering the possible changes we could make to sub_range. Ultimately sub_range is implemented as the original author intended.
As I had inferred, given how the documentation explicitly mentions the propagation of const'ness. [...]
How do you imagine the const / mutable underlying iterator would be chosen? If you are proposing that the const-ness of the sub_range template parameter is used, I think that this would break to much existing code. The current semantics have been in place since at least version 1.33.
That's a legitimate concern.
I do agree that sub_range's behaviour is rarely required or desirable. My approach has been to use iterator_range for most of my code. I imagine that you would prefer a class similar to iterator_range differing in so much as it takes a Range or const Range as the template parameter. I'll give this some more thought. I would like to avoid the proliferation of similar classes, and breaking interface changes, but I am motivated to provide a solution for your use case.
I'm not sure what to do either, but I'm not opposed to "do nothing" :/ Maybe add some rationale for why sub_range propagates const'ness. - Jeff

on Sat Jul 23 2011, Mathias Gaunard <mathias.gaunard-AT-ens-lyon.org> wrote:
On 07/23/2011 05:13 PM, Dave Abrahams wrote:
Well, if we want something like std::vector to be a Range, then we have to accept that at least *some* Ranges do propagate constness. And I'm pretty sure that we don't want other Ranges (std::pair<char*,char*>) to propagate constness. So unless the documentation says otherwise, I would assume that the concept doesn't say anything about whether constness is propagated. If you want to write generic Range code you have to account for both cases.
Constness is propagated for containers, yes, but sub_range is not a container, it is a view of another range, so it should [not] propagate the constness.
Really? Why not? If the Range concept said that views must not propagate constness, that would be one thing... but it doesn't, does it? -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Le 25/07/2011 21:38, Dave Abrahams a écrit :
If the Range concept said that views must not propagate constness, that would be one thing... but it doesn't, does it?
Thank you all for clarifying this. Like Mathias, I have the feeling that Views should not propagate constness, but as I understand it now, Views are only a special case (refinement ?) of Ranges. Maybe there is room for a View concept that will be more specific about constness propagatation ? I suggest two improvements in the documentation : 1. In the Range Concept Overview, add a note about constness propagation. Something along "the concept doesn't say anything about whether constness is propagated to the data. If you want to write generic Range code you have to account for both cases." 2. In the subrange, make the sentence "the sub_range class do propagate constness since it knows what a corresponding const_iterator is" bring out as an important admonition since it is an unexpected behaviour. Samuel

On Sat, Jul 23, 2011 at 4:13 PM, Dave Abrahams <dave@boostpro.com> wrote:
on Fri Jul 22 2011, Samuel Debionne <debionne-AT-hydrowide.com> wrote:
Hello everyone, Here is an example code that motivates my question :
<snip>
Well, if we want something like std::vector to be a Range, then we have to accept that at least *some* Ranges do propagate constness. And I'm pretty sure that we don't want other Ranges (std::pair<char*,char*>) to propagate constness. So unless the documentation says otherwise, I would assume that the concept doesn't say anything about whether constness is propagated. If you want to write generic Range code you have to account for both cases.
Currently the Range Concepts are not documented as propagating const-ness, but the sub_range class has been documented as propagating const-ness since at least Boost 1.33. The implementation uses boost::range_iterator<T>::type or boost::range_iterator<const T>::type as the return type for begin() and begin() const respectively. This means that sub_range supports all models of the Range Concept and behaves as intend for both examples you cited (std::pair<char*,char* and std::vector). My understanding of the original design is that it is one of the central purposes of the sub_range class. It seems that there is a desire to have some syntactic sugar when defining the iterator_range type, but we must select the const or mutable version of the iterator and hence it isn't immediately obvious how one would implement this in a manner that is more elegant than simply using iterator_range. My current belief is that the sub_range class does what was intended correctly, but it has quite limited usefulness. Changing the const-ness propagation of sub_range would be a nasty interface breakage and make the sub_range a very questionable abstraction since one would have to chose the const or mutable version of the Range iterator by an explicit means making it almost indistinguishable from iterator_range. There are a number of possible actions I can take. I could leave the code as it is and improve the documentation of sub_range. I could, if the confusion merits it, deprecate sub_range. I am very interested in feedback about the potential design decisions. I am particularly interested in alternative solutions that I have not considered.
-- Dave Abrahams BoostPro Computing http://www.boostpro.com
Regards, Neil Groves

On 07/24/2011 03:25 PM, Neil Groves wrote:
and make the sub_range a very questionable abstraction since one would have to chose the const or mutable version of the Range iterator by an explicit means making it almost indistinguishable from iterator_range.
I thought that was the point of sub_range: to be the same as iterator_range, except the template parameter it takes is a range rather than an iterator, simplifying things when only ranges and not iterators are involved.

Den 24-07-2011 16:04, Mathias Gaunard skrev:
On 07/24/2011 03:25 PM, Neil Groves wrote:
and make the sub_range a very questionable abstraction since one would have to chose the const or mutable version of the Range iterator by an explicit means making it almost indistinguishable from iterator_range.
I thought that was the point of sub_range: to be the same as iterator_range, except the template parameter it takes is a range rather than an iterator, simplifying things when only ranges and not iterators are involved.
+ propagation of constness. Consider using sub_range<T> as the type of a member variable. It would be very annoying in some cases that we cannot return a view that is const-correct and which requires us to construct a temporary view instead of just returning a reference to the existing view. -Thorsten
participants (7)
-
Dave Abrahams
-
Jeffrey Lee Hellrung, Jr.
-
Mathias Gaunard
-
Neil Groves
-
Olaf van der Spek
-
Samuel Debionne
-
Thorsten Ottosen