Boost C++ Libraries: Ticket #8011: iterator_range::size() is not SFINAE-friendly https://svn.boost.org/trac10/ticket/8011 <p> iterator_range&lt;I&gt;::size() is always defined, but fails a static assertion if I is not a random access iterator. This means it's not possible to use SFINAE To detect whether r.size() is valid. </p> <p> By moving the size() member to a new base type like the one below (and updating iterator_range to use this-&gt;m_Begin and this-&gt;m_End everywhere) the function will only be present when it can actually be used: </p> <pre class="wiki"> template&lt;class IteratorT, class CategoryT = typename std::iterator_traits&lt;IteratorT&gt;::iterator_category&gt; class iterator_range_base { protected: template&lt; class Iterator &gt; iterator_range_base( Iterator Begin, Iterator End ) : m_Begin(Begin), m_End(End) {} // begin and end iterators IteratorT m_Begin; IteratorT m_End; }; template&lt;class IteratorT&gt; class iterator_range_base&lt;IteratorT, std::random_access_iterator_tag&gt; { protected: template&lt; class Iterator &gt; iterator_range_base( Iterator Begin, Iterator End ) : m_Begin(Begin), m_End(End) {} typedef BOOST_DEDUCED_TYPENAME iterator_difference&lt;IteratorT&gt;::type difference_type; // begin and end iterators IteratorT m_Begin; IteratorT m_End; public: difference_type size() const { return m_End - m_Begin; } }; </pre><p> Also, the comment at the top of iterator_range_core.hpp says: </p> <blockquote class="citation"> <p> Defines the \c iterator_class and related functions. </p> </blockquote> <p> which should presumably say "iterator_range class" not "iterator_class" </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/8011 Trac 1.4.3 Nathan Ridge Mon, 11 Feb 2013 23:48:46 GMT <link>https://svn.boost.org/trac10/ticket/8011#comment:1 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:1</guid> <description> <p> Replying to <a class="closed ticket" href="https://svn.boost.org/trac10/ticket/8011" title="#8011: Feature Requests: iterator_range::size() is not SFINAE-friendly (closed: fixed)">Jonathan Wakely &lt;jwakely.boost@…&gt;</a>: </p> <blockquote class="citation"> <p> iterator_range&lt;I&gt;::size() is always defined, but fails a static assertion if I is not a random access iterator. This means it's not possible to use SFINAE To detect whether r.size() is valid. </p> </blockquote> <p> What is a use case for using SFINAE to detect whether r.size() is valid? (I'm not questioning that there is one, I'm just curious.) </p> </description> <category>Ticket</category> </item> <item> <author>Jonathan Wakely <jwakely.boost@…></author> <pubDate>Tue, 12 Feb 2013 15:36:14 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:2 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:2</guid> <description> <p> I have a <code>zip</code> function to iterate over a variable number of ranges: </p> <pre class="wiki">for (auto&amp; e : zip(container, array, other_ranges, ...)) /* ... */; </pre><p> In order to ensure iteration stops at the end of the shortest range I need to know the length of each range. I used to assume each range had a <code>r.size()</code> member, but that broke for <code>iterator_range</code> with non-random access iterators, so now I use <code>std::distance(std::begin(r), std::end(r))</code>, which is O(n). I would prefer to use <code>r.size()</code> if it's usable, but I can't use SFINAE to tell if <code>size()</code> is usable, because <code>iterator_range&lt;I&gt;::size()</code> is always defined, but not always usable. </p> <p> I could define a traits class to detect if the range is <code>iterator_range</code> and only use <code>size()</code> if it doesn't contain RA iterators, but I'd prefer to avoid that and just use SFINAE. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Nathan Ridge</dc:creator> <pubDate>Tue, 12 Feb 2013 18:32:45 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:3</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/8011#comment:2" title="Comment 2">Jonathan Wakely &lt;jwakely.boost@…&gt;</a>: </p> <blockquote class="citation"> <p> I have a <code>zip</code> function to iterate over a variable number of ranges: </p> <pre class="wiki">for (auto&amp; e : zip(container, array, other_ranges, ...)) /* ... */; </pre><p> In order to ensure iteration stops at the end of the shortest range I need to know the length of each range. I used to assume each range had a <code>r.size()</code> member, but that broke for <code>iterator_range</code> with non-random access iterators, so now I use <code>std::distance(std::begin(r), std::end(r))</code>, which is O(n). I would prefer to use <code>r.size()</code> if it's usable, but I can't use SFINAE to tell if <code>size()</code> is usable, because <code>iterator_range&lt;I&gt;::size()</code> is always defined, but not always usable. </p> <p> I could define a traits class to detect if the range is <code>iterator_range</code> and only use <code>size()</code> if it doesn't contain RA iterators, but I'd prefer to avoid that and just use SFINAE. </p> </blockquote> <p> Why not write zip() in such a way that it stops looping when the iterator of any one of the ranges reaches end()? </p> </description> <category>Ticket</category> </item> <item> <author>jwakely.boost@…</author> <pubDate>Wed, 13 Feb 2013 00:19:53 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:4</guid> <description> <p> I use <code>boost::zip_iterator</code>so to construct an end iterator that is reachable from {<a class="changeset" href="https://svn.boost.org/trac10/changeset/1" title="Import core sources for SVNmanger 0.38 ">r1</a>.begin(), <a class="changeset" href="https://svn.boost.org/trac10/changeset/2" title="Add Boost Disclaimer">r2</a>.begin(), ...} it needs to be {<a class="changeset" href="https://svn.boost.org/trac10/changeset/1" title="Import core sources for SVNmanger 0.38 ">r1</a>.begin()+N, <a class="changeset" href="https://svn.boost.org/trac10/changeset/2" title="Add Boost Disclaimer">r2</a>.begin()+N, ...} (where N is the size of the smallest range) not {<a class="changeset" href="https://svn.boost.org/trac10/changeset/1" title="Import core sources for SVNmanger 0.38 ">r1</a>.end(), <a class="changeset" href="https://svn.boost.org/trac10/changeset/2" title="Add Boost Disclaimer">r2</a>.end(), ...} because <code>operator==</code> for <code>zip_iterator</code> is only true if all members of the tuple are equal. </p> <p> <a class="ext-link" href="https://gitorious.org/redistd/redistd/blobs/master/include/redi/zip.h"><span class="icon">​</span>https://gitorious.org/redistd/redistd/blobs/master/include/redi/zip.h</a> Since I switched from using the size() member it needs fixing to avoid calling both <code>std::distance()</code> and <code>std::advance()</code>, but the point of this ticket is that I'd prefer to use size() when possible </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Nathan Ridge</dc:creator> <pubDate>Wed, 13 Feb 2013 01:06:03 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:5</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/8011#comment:4" title="Comment 4">jwakely.boost@…</a>: </p> <blockquote class="citation"> <p> I use <code>boost::zip_iterator</code>so to construct an end iterator that is reachable from {<a class="changeset" href="https://svn.boost.org/trac10/changeset/1" title="Import core sources for SVNmanger 0.38 ">r1</a>.begin(), <a class="changeset" href="https://svn.boost.org/trac10/changeset/2" title="Add Boost Disclaimer">r2</a>.begin(), ...} it needs to be {<a class="changeset" href="https://svn.boost.org/trac10/changeset/1" title="Import core sources for SVNmanger 0.38 ">r1</a>.begin()+N, <a class="changeset" href="https://svn.boost.org/trac10/changeset/2" title="Add Boost Disclaimer">r2</a>.begin()+N, ...} (where N is the size of the smallest range) not {<a class="changeset" href="https://svn.boost.org/trac10/changeset/1" title="Import core sources for SVNmanger 0.38 ">r1</a>.end(), <a class="changeset" href="https://svn.boost.org/trac10/changeset/2" title="Add Boost Disclaimer">r2</a>.end(), ...} because <code>operator==</code> for <code>zip_iterator</code> is only true if all members of the tuple are equal. </p> <p> <a class="ext-link" href="https://gitorious.org/redistd/redistd/blobs/master/include/redi/zip.h"><span class="icon">​</span>https://gitorious.org/redistd/redistd/blobs/master/include/redi/zip.h</a> Since I switched from using the size() member it needs fixing to avoid calling both <code>std::distance()</code> and <code>std::advance()</code>, but the point of this ticket is that I'd prefer to use size() when possible </p> </blockquote> <p> Let me suggest an alternative way of implementing such a zipped range: </p> <ul><li>Use a custom iterator class rather than boost::zip_iterator <ul><li>Use a special marker value to represent 'end' </li><li>In the begin iterator, store the end of each component range as well as the current position in each </li></ul></li><li>When incrementing the begin iterator, increment each component iterator. When any one of them compare equal to the corresponding end iterator, set the iterator's value to the special marker value that represents 'end' </li></ul><p> This way: </p> <ul><li>You don't do an extra traversal of forward and bidirectional component ranges to figure out their size </li><li>You range is usable with single-pass component ranges </li></ul><p> The only disadvantage I can see is the extra memory to store the component end iterators in the zipped range's iterator. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Nathan Ridge</dc:creator> <pubDate>Wed, 13 Feb 2013 01:09:04 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:6</guid> <description> <p> Regarding the original request for making iterator_range::size() SFINAE-friendly: I am a little hesitant about it because of the effect on the code's readability. I am curious: </p> <ul><li>Do you recommend this as a general technique to apply to member functions of templates where the member function only makes sense for a subset of the template parameters? If not, what makes iterator_range::size() special? </li><li>Do you see this technique used commonly elsewhere (for example, libstdc++)? </li></ul><p> (where by "this technique" I mean "factoring out the member function into a base class that's specialized for the relevant subset of template parameters"). </p> </description> <category>Ticket</category> </item> <item> <author>jwakely.boost@…</author> <pubDate>Wed, 13 Feb 2013 08:26:09 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:7 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:7</guid> <description> <p> Thank you for actually addressing the subject, feel free to write your own custom iterator that way if you want to! :-) </p> <p> I see my request as analogous to the standard's requirement that a function shall not participate in overload resolution if it doesn't meet the required concepts. The size() member isn't a function template so can't use disable_if directly, hence my suggestion to move it to a base class that can be partially specialized, but I don't care how it's done, I just think it should be done. </p> </description> <category>Ticket</category> </item> <item> <author>jwakely.boost@…</author> <pubDate>Wed, 13 Feb 2013 10:01:12 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:8 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:8</guid> <description> <p> Another analogy would be std::forward_list, it has no size() member. It doesn't have a size member that fails a static assertion. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Nathan Ridge</dc:creator> <pubDate>Mon, 18 Feb 2013 05:34:43 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:9 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:9</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/8011#comment:7" title="Comment 7">jwakely.boost@…</a>: </p> <blockquote class="citation"> <p> I see my request as analogous to the standard's requirement that a function shall not participate in overload resolution if it doesn't meet the required concepts. The size() member isn't a function template so can't use disable_if directly, hence my suggestion to move it to a base class that can be partially specialized, but I don't care how it's done, I just think it should be done. </p> </blockquote> <p> I started a thread on the boost mailing list about this topic: <a class="ext-link" href="http://thread.gmane.org/gmane.comp.lib.boost.devel/238845"><span class="icon">​</span>http://thread.gmane.org/gmane.comp.lib.boost.devel/238845</a> </p> <p> Feel free to weigh in. I will implement the workaround if it has concensus. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Nathan Ridge</dc:creator> <pubDate>Mon, 18 Feb 2013 06:47:37 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:10 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:10</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/8011#comment:2" title="Comment 2">Jonathan Wakely &lt;jwakely.boost@…&gt;</a>: </p> <blockquote class="citation"> <p> I used to assume each range had a <code>r.size()</code> member, but that broke for <code>iterator_range</code> with non-random access iterators, so now I use <code>std::distance(std::begin(r), std::end(r))</code>, which is O(n). I would prefer to use <code>r.size()</code> if it's usable </p> </blockquote> <p> Actually, according to section 24.4.4/4 [iterator.operations] of the standard, std::distance is O(1) for random-access iterators. So you can just always use std::distance. </p> <p> In light of this, do you still think there is a use case for what you are requesting? </p> </description> <category>Ticket</category> </item> <item> <author>jwakely.boost@…</author> <pubDate>Mon, 18 Feb 2013 09:32:19 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:11 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:11</guid> <description> <blockquote class="citation"> <p> Feel free to weigh in. </p> </blockquote> <p> I'd rather not, the boost lists are an enormous time sink. The only reply is typical: offering advice without bothering to read the ticket you linked to, which provides the use case. The reason iterator_range::size() doesn't use std::distance() is to guarantee it's O(1). The same reason std::forward_list doesn't provide size() (but it *doesn't provide* it, rather than providing it and failing a static assertion.) </p> <blockquote class="citation"> <p> So you can just always use std::distance. </p> </blockquote> <p> Solving the problem for RA iterators was never the issue, obviously std::distance works for them, but that's sub-optimal for non-RA iterators. If a container such as std::map, which has bidirectional iterators, provides an O(1) size() member then I want to use it. But I can't tell whether a range provides size() or not, because iterator_range provides it but it isn't usable. </p> <p> I'm currently tempted just to say my type doesn't support boost::iterator_range and be done with it. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Nathan Ridge</dc:creator> <pubDate>Mon, 18 Feb 2013 10:15:03 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:12 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:12</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/8011#comment:11" title="Comment 11">jwakely.boost@…</a>: </p> <blockquote class="citation"> <blockquote class="citation"> <p> So you can just always use std::distance. </p> </blockquote> <p> Solving the problem for RA iterators was never the issue, obviously std::distance works for them, but that's sub-optimal for non-RA iterators. If a container such as std::map, which has bidirectional iterators, provides an O(1) size() member then I want to use it. But I can't tell whether a range provides size() or not, because iterator_range provides it but it isn't usable. </p> </blockquote> <p> Ah, I see now. Thanks for clarifying! </p> <blockquote class="citation"> <blockquote class="citation"> <p> Feel free to weigh in. </p> </blockquote> <p> I'd rather not, the boost lists are an enormous time sink. The only reply is typical: offering advice without bothering to read the ticket you linked to, which provides the use case. </p> </blockquote> <p> I'm sorry to hear you have such a poor opinion of the boost lists. I am happy to make your case for you, now that I understand it. </p> </description> <category>Ticket</category> </item> <item> <author>Jonathan Wakely <jwakely.boost@…></author> <pubDate>Mon, 18 Feb 2013 11:11:31 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8011#comment:13 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8011#comment:13</guid> <description> <p> I decided to bite the bullet and resubscribe to the list, although your summary of the issue is perfect so I hope I won't have much to add. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Neil Groves</dc:creator> <pubDate>Mon, 24 Feb 2014 18:32:47 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/8011#comment:14 https://svn.boost.org/trac10/ticket/8011#comment:14 <ul> <li><strong>status</strong> <span class="trac-field-old">new</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">fixed</span> </li> </ul> <p> I strongly agree with Jonathan Wakely on this matter. It is something I have wanted to improve for quite some time. </p> <p> I want to go slightly further since there are other member functions that make sense for bidirectional traversal and then some more for random_access traversal. I'll clean this all up in one iteration. </p> Ticket