Re: [boost] [iterator] UB when implicitly using default constructed counting_iterator<unsigned>

Hi Jeff On 01.12.2012, at 02:53, Jeffrey Lee Hellrung, Jr. wrote:
And a default initialized scalar type, isn't.
"isn't"...what, exactly?
it isn't initialized with a defined value. struct X{ X(){} unsigned x; }; void foo(){ X y; // -> y.x has an uninitialized value AFAIK if it is local } that is the situation we run into with the default constructed counting_iterator<unsigned>()
I'm not opposed to giving defined behavior to a default constructed counting_iterator< [integral type] >, I'm just not sure what that defined behavior should be. Maybe I'm over-analyzing it...
I still believe the most simple change I suggested is enough (at least it would cover my situation well enough). Those input iterators where a default constructed one is the end iterator is IMHO more of an exception, because it is a special case for the stream iterators. Your idea with std::numberic_limits< Iterator >::max() requires a much more complex solution with template meta programming, i.e., enable_if<is_arithmetic<Iterator>> or similar. And it won't provide the default value of Iterator, which is zero in the case of the built-in arithmetic types, i.e., unsigned(). What do you think? Regards Peter. -- Prof. Peter Sommerlad Institut für Software: Bessere Software - Einfach, Schneller! HSR Hochschule für Technik Rapperswil Oberseestr 10, Postfach 1475, CH-8640 Rapperswil http://ifs.hsr.ch http://cute-test.com http://linticator.com http://includator.com tel:+41 55 222 49 84 == mobile:+41 79 432 23 32 fax:+41 55 222 46 29 == mailto:peter.sommerlad@hsr.ch

AMDG On 12/01/2012 07:20 AM, Peter Sommerlad wrote:
Hi Jeff
On 01.12.2012, at 02:53, Jeffrey Lee Hellrung, Jr. wrote:
And a default initialized scalar type, isn't.
"isn't"...what, exactly?
it isn't initialized with a defined value.
struct X{ X(){} unsigned x; }; void foo(){ X y; // -> y.x has an uninitialized value AFAIK if it is local } that is the situation we run into with the default constructed counting_iterator<unsigned>()
IMO, this is a good thing. Any use of a default constructed counting_iterator is probably wrong, no matter what (arbitrary) value is chosen. As such, it's better to leave it uninitialized, so these errors can be caught by tools like valgrind.
I'm not opposed to giving defined behavior to a default constructed counting_iterator< [integral type] >, I'm just not sure what that defined behavior should be. Maybe I'm over-analyzing it...
I still believe the most simple change I suggested is enough (at least it would cover my situation well enoughi).
Your use case is very error prone. It only works because you started the iteration at 1, not 0.
Those input iterators where a default constructed one is the end iterator is IMHO more of an exception, because it is a special case for the stream iterators.
Any other iterator gives undefined behavior when it's default constructed. I don't see why counting_iterator should be different.
Your idea with std::numberic_limits< Iterator >::max() requires a much more complex solution with template meta programming, i.e., enable_if<is_arithmetic<Iterator>> or similar. And it won't provide the default value of Iterator, which is zero in the case of the built-in arithmetic types, i.e., unsigned().
What do you think?
In Christ, Steven Watanabe

On 01/12/12 18:39, Steven Watanabe wrote:
AMDG
On 12/01/2012 07:20 AM, Peter Sommerlad wrote:
Hi Jeff
On 01.12.2012, at 02:53, Jeffrey Lee Hellrung, Jr. wrote:
And a default initialized scalar type, isn't.
"isn't"...what, exactly?
it isn't initialized with a defined value.
struct X{ X(){} unsigned x; }; void foo(){ X y; // -> y.x has an uninitialized value AFAIK if it is local } that is the situation we run into with the default constructed counting_iterator<unsigned>()
IMO, this is a good thing. Any use of a default constructed counting_iterator is probably wrong, no matter what (arbitrary) value is chosen. As such, it's better to leave it uninitialized, so these errors can be caught by tools like valgrind.
I'm not opposed to giving defined behavior to a default constructed counting_iterator< [integral type] >, I'm just not sure what that defined behavior should be. Maybe I'm over-analyzing it...
I still believe the most simple change I suggested is enough (at least it would cover my situation well enoughi).
Your use case is very error prone. It only works because you started the iteration at 1, not 0.
Those input iterators where a default constructed one is the end iterator is IMHO more of an exception, because it is a special case for the stream iterators.
Any other iterator gives undefined behavior when it's default constructed. I don't see why counting_iterator should be different.
Your idea with std::numberic_limits< Iterator >::max() requires a much more complex solution with template meta programming, i.e., enable_if<is_arithmetic<Iterator>> or similar. And it won't provide the default value of Iterator, which is zero in the case of the built-in arithmetic types, i.e., unsigned().
What do you think?
In Christ, Steven Watanabe
Accidentally I just encountered a similar problem in my code, which was caused by counting_iterator not beeing initialised and it took me a while to debug it. I support Peter's suggestion to initialise counting_iterator<T> with T() because it forwards the default behaviour of T, which will become inaccessible in template code otherwise. Consider e.g. a simple template of the form template<class IT> struct Range { IT first, last; Range(void)= default; size_t size(void) const {return last - first;} }; The behaviour of size() is currently undefined for default constructed Range objects, which is a major disadvantage in my opinion, since any reproducible default value will result in the expected behaviour of size zero for a default constructed Range object. This is what all other iterator implementations guarantee, too, if I am not mistaken (Please correct me if I am wrong here). Furthermore I interpret counting_iterator as a wrapper around objects of type T, so forwarding the default constructor of T is desirable from my point of view, mainly because there is no way to reproduce its behaviour, once the information about the iterator type is lost (as is the case in the example above) Just my two cents. Regards Claas

AMDG On 12/03/2012 06:07 AM, "Claas H. Köhler" wrote:
Consider e.g. a simple template of the form
template<class IT> struct Range { IT first, last;
Range(void)= default;
size_t size(void) const {return last - first;} };
I do not know of any iterator for which Range<II> r; std::size_t size = r.size(); is legal.
The behaviour of size() is currently undefined for default constructed Range objects, which is a major disadvantage in my opinion, since any reproducible default value will result in the expected behaviour of size zero for a default constructed Range object. This is what all other iterator implementations guarantee, too, if I am not mistaken (Please correct me if I am wrong here).
Most iterators do not make this guarantee: std::vector/list/string/deque/map/set<T>::iterator - default constructed iterators may be singular. value initialization may not make any difference. T* - default constructed iterator is uninitialized std::istream_iterator - okay, end std::istreambuf_iterator - okay, end In Christ, Steven Watanabe

On 03/12/12 21:24, Steven Watanabe wrote:
AMDG
On 12/03/2012 06:07 AM, "Claas H. Köhler" wrote:
Consider e.g. a simple template of the form
template<class IT> struct Range { IT first, last;
Range(void)= default;
size_t size(void) const {return last - first;} };
I do not know of any iterator for which
Range<II> r; std::size_t size = r.size();
is legal.
The behaviour of size() is currently undefined for default constructed Range objects, which is a major disadvantage in my opinion, since any reproducible default value will result in the expected behaviour of size zero for a default constructed Range object. This is what all other iterator implementations guarantee, too, if I am not mistaken (Please correct me if I am wrong here).
Most iterators do not make this guarantee:
std::vector/list/string/deque/map/set<T>::iterator - default constructed iterators may be singular. value initialization may not make any difference. T* - default constructed iterator is uninitialized std::istream_iterator - okay, end std::istreambuf_iterator - okay, end On my machine (gcc-4.7 with linux) the iterators of vector, list, set and string are all initialised to something equivalent to zero. T* can be forced to obtain a reproducible value by writing
typedef T* X; X x= X(), which will also result in well defined behaviour when default constructing an element, which is highly desirable in my opinion. What is your suggestion to achieve something similar with the current implementation of couting_iterator? What are the drawbacks of Peter's suggestion from your point of view? Regards Claas
In Christ, Steven Watanabe
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Deutsches Zentrum für Luft- und Raumfahrt e.V. (DLR) Institut für Methodik der Fernerkundung | Experimentelle Verfahren | Münchner Str | 82234 Weßling Claas H. Köhler Telefon 08153 28-1274 | Telefax 08153 28-1337 | claas.koehler@dlr.de www.DLR.de/EOC

On Mon, Dec 3, 2012 at 6:07 AM, "Claas H. Köhler" <claas.koehler@dlr.de>wrote:
On 01/12/12 18:39, Steven Watanabe wrote:
AMDG
On 12/01/2012 07:20 AM, Peter Sommerlad wrote:
Hi Jeff
On 01.12.2012, at 02:53, Jeffrey Lee Hellrung, Jr. wrote:
And a default initialized scalar type, isn't.
"isn't"...what, exactly?
it isn't initialized with a defined value.
struct X{ X(){} unsigned x; }; void foo(){ X y; // -> y.x has an uninitialized value AFAIK if it is local } that is the situation we run into with the default constructed counting_iterator<unsigned>()
IMO, this is a good thing. Any use of a default constructed counting_iterator is probably wrong, no matter what (arbitrary) value is chosen. As such, it's better to leave it uninitialized, so these errors can be caught by tools like valgrind.
I'm not opposed to giving defined behavior to a default constructed counting_iterator< [integral type] >, I'm just not sure what that defined behavior should be. Maybe I'm over-analyzing it...
I still believe the most simple change I suggested is enough (at least it would cover my situation well enoughi).
Your use case is very error prone. It only works because you started the iteration at 1, not 0.
Those input iterators where a default constructed one is the end iterator is IMHO more of an exception, because it is a special case for the stream iterators.
Any other iterator gives undefined behavior when it's default constructed. I don't see why counting_iterator should be different.
Your idea with std::numberic_limits< Iterator >::max() requires a much more complex solution with template meta programming, i.e., enable_if<is_arithmetic<Iterator>> or similar. And it won't provide the default value of Iterator, which is zero in the case of the built-in arithmetic types, i.e., unsigned().
What do you think?
In Christ, Steven Watanabe
Accidentally I just encountered a similar problem in my code, which was caused by counting_iterator not beeing initialised and it took me a while to debug it.
To be precise, the cause of your problem is your use of a default-constructed counting_iterator, not the behavior of a default-constructed counting_iterator, right? I support Peter's suggestion to initialise counting_iterator<T> with T()
because it forwards the default behaviour of T, which will become inaccessible in template code otherwise.
I'm not sure I follow. Can't you just use counting_iterator<T>::counting_iterator(T) ? [...]
Furthermore I interpret counting_iterator as a wrapper around objects of type T, so forwarding the default constructor of T is desirable from my point of view, mainly because there is no way to reproduce its behaviour, once the information about the iterator type is lost (as is the case in the example above)
That may be true in general, but a default-constructed counting_iterator< [integral type] > should really never be used in iteration. I might even go so far as to suggest disabling the default constructor (or adding a static assertion), but that might be too restrictive (it's fine and sometimes convenient to default-construct and assign, for example). Furthermore, building on Steven's prior point, a default-constructed counting_iterator which 0-initializes could actually hide incorrect usage (see original example with primes, which works non-obviously with the proposed behavior change) that wouldn't be picked up by external tools. I just haven't seen a valid use case where 0-initializing the value of a default-constructed counting_iterator< [integral type] > is desirable other than to help identify incorrect usage, but I'm not even convinced it would do that :/ - Jeff

On 04/12/12 18:42, Jeffrey Lee Hellrung, Jr. wrote:
On Mon, Dec 3, 2012 at 6:07 AM, "Claas H. Köhler" <claas.koehler@dlr.de>wrote:
On 01/12/12 18:39, Steven Watanabe wrote:
AMDG
On 12/01/2012 07:20 AM, Peter Sommerlad wrote:
Hi Jeff
On 01.12.2012, at 02:53, Jeffrey Lee Hellrung, Jr. wrote:
And a default initialized scalar type, isn't.
"isn't"...what, exactly?
it isn't initialized with a defined value.
struct X{ X(){} unsigned x; }; void foo(){ X y; // -> y.x has an uninitialized value AFAIK if it is local } that is the situation we run into with the default constructed counting_iterator<unsigned>()
IMO, this is a good thing. Any use of a default constructed counting_iterator is probably wrong, no matter what (arbitrary) value is chosen. As such, it's better to leave it uninitialized, so these errors can be caught by tools like valgrind.
I'm not opposed to giving defined behavior to a default constructed counting_iterator< [integral type] >, I'm just not sure what that defined behavior should be. Maybe I'm over-analyzing it...
I still believe the most simple change I suggested is enough (at least it would cover my situation well enoughi).
Your use case is very error prone. It only works because you started the iteration at 1, not 0.
Those input iterators where a default constructed one is the end iterator is IMHO more of an exception, because it is a special case for the stream iterators.
Any other iterator gives undefined behavior when it's default constructed. I don't see why counting_iterator should be different.
Your idea with std::numberic_limits< Iterator >::max() requires a much more complex solution with template meta programming, i.e., enable_if<is_arithmetic<Iterator>> or similar. And it won't provide the default value of Iterator, which is zero in the case of the built-in arithmetic types, i.e., unsigned().
What do you think?
In Christ, Steven Watanabe
Accidentally I just encountered a similar problem in my code, which was caused by counting_iterator not beeing initialised and it took me a while to debug it.
To be precise, the cause of your problem is your use of a default-constructed counting_iterator, not the behavior of a default-constructed counting_iterator, right? To be very precise, it was actually the assumption that counting_iterator behaves similar to all other iterators in the standard library, which guarantee a defined behaviour when default constructed. (By guaranteed I mean as far as I have seen so far. Not sure what the standard says about them)
I support Peter's suggestion to initialise counting_iterator<T> with T()
because it forwards the default behaviour of T, which will become inaccessible in template code otherwise.
I'm not sure I follow. Can't you just use counting_iterator<T>::counting_iterator(T) ?
[...]
Most of the time no, because typically I have a template parameter for the iterator, i.e. something like template<class IT> void doSomething(IT first, IT last){ while(first != last){...} } Thus the information about the template parameter passed to counting_iterator is lost, unless you specialise overloads for counting_iterator. By the way, has anyone of you ever seen a case, where a loop similar to the one above leads to undefined behaviour, if two default constructed (standard library) iterators of any kind are passed?
Furthermore I interpret counting_iterator as a wrapper around objects of type T, so forwarding the default constructor of T is desirable from my point of view, mainly because there is no way to reproduce its behaviour, once the information about the iterator type is lost (as is the case in the example above)
That may be true in general, but a default-constructed counting_iterator< [integral type] > should really never be used in iteration. I might even go so far as to suggest disabling the default constructor (or adding a static assertion), but that might be too restrictive (it's fine and sometimes convenient to default-construct and assign, for example). Furthermore, building on Steven's prior point, a default-constructed counting_iterator which 0-initializes could actually hide incorrect usage (see original example with primes, which works non-obviously with the proposed behavior change) that wouldn't be picked up by external tools. I think this problem is more related to the underlying base class of the counting_iterator, since integers do not provide an "invalid value" in contrast to pointers (underlying most other iterators), which can be NULL to signal.
Considering your opinion, that uninitialised values for numeric types are preferable to zero-initialised ones, since they are easier to trace back by external tools: Can you name any place in the standard library, where this strategy is used? std::complex<T>, e.g. does not follow this strategy, since the default constructor initialises real and imaginary part to T().
I just haven't seen a valid use case where 0-initializing the value of a default-constructed counting_iterator< [integral type] > is desirable other than to help identify incorrect usage, but I'm not even convinced it would do that :/
- Jeff
One use case would be a loop, such as the one posted above. I would personally favour this loop to quit immediately, when two default constructed iterators are passed. A similar use case is the boost::iterator_range, which is not guaranteed to be empty, if default constructed for the counting_iterator. In fact, that was actually the problem I encountered. Consider code like this template<class R> struct RangeWrapper{ [...] bool empty(void) const { return range.begin() == range.end(); } R range; }; I have not found a way (other than specialising for R= iterator_range<counting_iterator>) to define a default constructor, such that empty is guaranteed to return true for default constructed RangeWrapper instances.
From my point of view, the biggest draw back is, that the current implementation behaves differently from any other iterator in (my implementation of) the standard library I have worked with so-far.

On Wed, Dec 5, 2012 at 8:53 AM, "Claas H. Köhler" <claas.koehler@dlr.de> wrote:
To be very precise, it was actually the assumption that counting_iterator behaves similar to all other iterators in the standard library, which guarantee a defined behaviour when default constructed. (By guaranteed I mean as far as I have seen so far. Not sure what the standard says about them)
AFAIK you can't expect default constructed iterators to be usable (in general). For example, this will assert in VC11: typedef std::map<int, int> C; C::iterator a; C::iterator b; a == b; Olaf

On 05/12/12 09:48, Olaf van der Spek wrote:
On Wed, Dec 5, 2012 at 8:53 AM, "Claas H. Köhler" <claas.koehler@dlr.de> wrote:
To be very precise, it was actually the assumption that counting_iterator behaves similar to all other iterators in the standard library, which guarantee a defined behaviour when default constructed. (By guaranteed I mean as far as I have seen so far. Not sure what the standard says about them)
AFAIK you can't expect default constructed iterators to be usable (in general). For example, this will assert in VC11: typedef std::map<int, int> C; C::iterator a; C::iterator b; a == b;
Hi Olaf! Thanks for the info. That's very interesting. So it seems to be implementation dependent. Using gcc-4.7 the following code outputs true/ 1 for all 5 comparisons. std::vector<double>::iterator itv1, itv2; std::string::iterator itstr1, itstr2; std::set<double>::iterator its1, its2; std::map<double, double>::iterator itm1, itm2; std::list<double>::iterator itl1, itl2; std::cout << (its1 == its2) <<std::endl << (itv1 == itv2) <<std::endl << (itstr1 == itstr2) <<std::endl << (itm1 == itm2) <<std::endl << (itl1 == itl2) <<std::endl; Claas

On Wed, Dec 5, 2012 at 1:42 PM, "Claas H. Köhler" <claas.koehler@dlr.de> wrote:
Thanks for the info. That's very interesting. So it seems to be implementation dependent. Using gcc-4.7 the following code outputs true/ 1 for all 5 comparisons.
std::vector<double>::iterator itv1, itv2; std::string::iterator itstr1, itstr2; std::set<double>::iterator its1, its2; std::map<double, double>::iterator itm1, itm2; std::list<double>::iterator itl1, itl2;
std::cout << (its1 == its2) <<std::endl << (itv1 == itv2) <<std::endl << (itstr1 == itstr2) <<std::endl << (itm1 == itm2) <<std::endl << (itl1 == itl2) <<std::endl;
Did you try to enable iterator debugging? Anyway, this can't be relied on. -- Olaf

On 05/12/12 13:48, Olaf van der Spek wrote:
Did you try to enable iterator debugging? Good point. Originally no. But I retried replacing <vector> by <debug/vector> and accordingly for the other container #includes and there is no difference in output nor asserts.
Anyway, this can't be relied on. Agreed. The question is whether one implementation is preferable over the other. I personally favour the version featuring well defined behaviour.

On 05/12/12 14:18, "Claas H. Köhler" wrote:
On 05/12/12 13:48, Olaf van der Spek wrote:
Did you try to enable iterator debugging? Good point. Originally no. But I retried replacing <vector> by <debug/vector> and accordingly for the other container #includes and there is no difference in output nor asserts.
Update: When using the -D_GLIBCXX_DEBUG flag, the code indeed asserts with the following message: "attempt to compare a singular iterator to a singular iterator" I guess this also means, that it is possible to detect singular iterators even if they are initialised upon default construction.

On Wed, Dec 5, 2012 at 2:40 PM, "Claas H. Köhler" <claas.koehler@dlr.de> wrote:
I guess this also means, that it is possible to detect singular iterators even if they are initialised upon default construction.
Only if they've got special debug code and not if they're types like int. -- Olaf

On Wed, Dec 5, 2012 at 2:40 PM, "Claas H. Köhler" <claas.koehler@dlr.de> wrote:
I guess this also means, that it is possible to detect singular iterators even if they are initialised upon default construction.
Only if they've got special debug code and not if they're types like int. Correct. Basically this would require a boost equivalent of the safe_iterator (that's what its called in gcc), which could be used to wrap the boost iterators for debugging. Although this is not a
On 05/12/12 16:44, Olaf van der Spek wrote: trivial task, it seems preferable to me over leaving members uninitialised for the sake of (arguably) improved debugging capability.

Just to clarify A singular iterator can be safely compared with other iterators. It can not be dereferenced or incremented. A default constructed boost::counting_iterator<unsigned> can not safely be compared. send from a mobile device. Prof. Peter Sommerlad peter.Sommerlad@hsr.ch +41-79-432 23 32 On 05.12.2012, at 17:19, "Claas H. Köhler" <claas.koehler@dlr.de> wrote:
On Wed, Dec 5, 2012 at 2:40 PM, "Claas H. Köhler" <claas.koehler@dlr.de> wrote:
I guess this also means, that it is possible to detect singular iterators even if they are initialised upon default construction.
Only if they've got special debug code and not if they're types like int. Correct. Basically this would require a boost equivalent of the safe_iterator (that's what its called in gcc), which could be used to wrap the boost iterators for debugging. Although this is not a
On 05/12/12 16:44, Olaf van der Spek wrote: trivial task, it seems preferable to me over leaving members uninitialised for the sake of (arguably) improved debugging capability.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Just to clarify A singular iterator can be safely compared with other iterators. It can not be dereferenced or incremented. A default constructed boost::counting_iterator<unsigned> can not safely be compared. Can you cite a part of the standard which guarantees this? I only found 24.2.1, number 5 (in my
On 05/12/12 17:31, Peter Sommerlad wrote: preliminary version of the C++11 standard) <quote> Results of most expressions are undefined for singular values; the only exceptions are destroying an iterator that holds a singular value, the assignment of a non-singular value to an iterator that holds a singular value, and, for iterators that satisfy the DefaultConstructible requirements, using a value-initialized iterator as the source of a copy or move operation. [ Note: This guarantee is not offered for default initialization, although the distinction only matters for types with trivial default constructors such as pointers or aggregates holding pointers. — end note ] In these cases the singular value is overwritten the same way as any other value. Dereferenceable values are always non-singular. <\quote>
From which part do you conclude that comparisons are well-defined?

On 05/12/12 07:42, "Claas H. Köhler" wrote:
Thanks for the info. That's very interesting. So it seems to be implementation dependent. Using gcc-4.7 the following code outputs true/ 1 for all 5 comparisons.
std::vector<double>::iterator itv1, itv2; std::string::iterator itstr1, itstr2; std::set<double>::iterator its1, its2; std::map<double, double>::iterator itm1, itm2; std::list<double>::iterator itl1, itl2;
std::cout << (its1 == its2) <<std::endl << (itv1 == itv2) <<std::endl << (itstr1 == itstr2) <<std::endl << (itm1 == itm2) <<std::endl << (itl1 == itl2) <<std::endl;
Did you look at std::array's iterator? That should be just a raw pointer (I believe the standard guarantees that?), so will behave in a similar way to counting_iterator. John Bytheway

[John Bytheway]
Did you look at std::array's iterator? That should be just a raw pointer (I believe the standard guarantees that?)
std::array iterators can be classes (and are, in VC's implementation). Stephan T. Lavavej Visual C++ Libraries Developer

On 06/12/12 00:21, John Bytheway wrote:
On 05/12/12 07:42, "Claas H. Köhler" wrote:
Thanks for the info. That's very interesting. So it seems to be implementation dependent. Using gcc-4.7 the following code outputs true/ 1 for all 5 comparisons.
std::vector<double>::iterator itv1, itv2; std::string::iterator itstr1, itstr2; std::set<double>::iterator its1, its2; std::map<double, double>::iterator itm1, itm2; std::list<double>::iterator itl1, itl2;
std::cout << (its1 == its2) <<std::endl << (itv1 == itv2) <<std::endl << (itstr1 == itstr2) <<std::endl << (itm1 == itm2) <<std::endl << (itl1 == itl2) <<std::endl;
Did you look at std::array's iterator? That should be just a raw pointer (I believe the standard guarantees that?), so will behave in a similar way to counting_iterator. Hi John. You are correct. The following code returns false/0 (with gcc-4.7):
typedef std::array<double, 10>::iterator T; T ita1, ita2; std::cout << (ita1 == ita2) <<std::endl; However, you can tweak it like this: typedef std::array<double, 10>::iterator T; T ita1=T(), ita2=T(); std::cout << (ita1 == ita2) <<std::endl; and it returns true /1. It is mainly the lack of this functionality which is on my wish-list for the counting_iterator. Regards Claas

AMDG On 12/06/2012 12:10 AM, "Claas H. Köhler" wrote:
Hi John. You are correct. The following code returns false/0 (with gcc-4.7):
typedef std::array<double, 10>::iterator T; T ita1, ita2;
std::cout << (ita1 == ita2) <<std::endl;
However, you can tweak it like this:
typedef std::array<double, 10>::iterator T; T ita1=T(), ita2=T();
std::cout << (ita1 == ita2) <<std::endl;
and it returns true /1. It is mainly the lack of this functionality which is on my wish-list for the counting_iterator.
This is still undefined behavior. "Iterators can also have singular values that are not associated with any sequence. ... Results of most expressions are undefined for singular values; the only exceptions are destroying an iterator that holds a singular value, the assignment of a non-singular value to an iterator that hold a singular value, and, for iterators that satisfy the DefaultConstructible requirements, using a value-initialized iterator as the source of a copy or move operation." (C++11 24.2.1) In Christ, Steven Watanabe

Hi Steven! On 06/12/12 18:10, Steven Watanabe wrote:
AMDG
On 12/06/2012 12:10 AM, "Claas H. Köhler" wrote:
Hi John. You are correct. The following code returns false/0 (with gcc-4.7):
typedef std::array<double, 10>::iterator T; T ita1, ita2;
std::cout << (ita1 == ita2) <<std::endl;
However, you can tweak it like this:
typedef std::array<double, 10>::iterator T; T ita1=T(), ita2=T();
std::cout << (ita1 == ita2) <<std::endl;
and it returns true /1. It is mainly the lack of this functionality which is on my wish-list for the counting_iterator.
This is still undefined behavior.
Maybe I did not express myself clearly enough, so I'll try again to explain my argument: I agree that the behaviour of the examples is undefined in the sense, that the standard does not prescribe its outcome, except for T= T() when T is a scalar type/pointer[...]. That's what I tried to express when I cited the exact same paragraph you cited in response to an earlier message. (it is nice to know though, that your copy of the standard is apparently identical to mine, regarding this paragraph :-)). Unfortunately the standard will not offer a general solution in this case, since it basically tells us to do whatever we want to. The implementation of the standard library shipped with gcc (that's the only one I worked with so far), does guarantee, that the output of the examples I posted is the same every time you call it, so the behaviour is well defined in the sense that the outcome is predictable. Apparently the VC11 implementation behaves differently as Olaf pointed out. So the actual question is, whether boost should adopt the strategy used in the gcc implementation, which allows to default construct the counting_iterator such that the output is predictable, (which I favour) or whether it should not. There has been only one argument which actually speaks against initialising the counting_iterator, because it will prevent one from using a simple debugger to identify malformed code. From my point of view this argument does not hold, since - it helps you only in situations, which could mostly be prevented if the iterator was initialised - iterator debugging is preferably done using additional functionality built into most implementations of the standard library, as pointed out by Olaf. So if boost wanted to offer this kind of debugging capability, it should be implemented in form of a general debug wrapper for iterators. But this is just my opinion, and if it is not shared by a majority of boost users, it is clearly not worth to change the implementation. Regards Claas

On Fri, Dec 7, 2012 at 8:57 AM, "Claas H. Köhler" <claas.koehler@dlr.de> wrote:
There has been only one argument which actually speaks against initialising the counting_iterator, because it will prevent one from using a simple debugger to identify malformed code. From my point of view this argument does not hold, since - it helps you only in situations, which could mostly be prevented if the iterator was initialised - iterator debugging is preferably done using additional functionality built into most implementations of the standard library, as pointed out by Olaf. So if boost wanted to offer this kind of debugging capability, it should be implemented in form of a general debug wrapper for iterators.
There are two choices: 1. Undefined Behavior 2. Defined but (probably) wrong behavior that can't be automatically detected by some tools. I don't think 2 is better. -- Olaf
participants (7)
-
"Claas H. Köhler"
-
Jeffrey Lee Hellrung, Jr.
-
John Bytheway
-
Olaf van der Spek
-
Peter Sommerlad
-
Stephan T. Lavavej
-
Steven Watanabe