[type_traits] Interest in is_iterator type trait?
Back in 2011, Howard Hinnant and I put together an is_iterator type trait based on code Howard had developed and used over a period of time. See http://beman.github.io/is_iterator/is_iterator.hpp for the actual code See http://beman.github.io/is_iterator/is_iterator_test.cpp for a test program. The intent was always to submit is_iterator to Boost, but we never got around to it. Now I need it in Boost.Filesystem, so I'd rather see it go in type traits than just sticking it into boost/filesystem/detail. If there is interest, and John Maddock gives the OK, I'll write docs and integrate into type_traits, then submit a pull request. --Beman
Here's something that I have been using on C++03 compilers to check if its an iterator. It is somewhat similar, except it checks additionally for `reference` and `pointer`. template<class T1 = void, class T2 = void, class T3 = void, class T4 = void, class T5 = void> struct holder { typedef void type; }; template<class T, class X = void> struct is_iterator : boost::mpl::bool_<false> {}; template<class T> struct is_iterator<T*> : boost::mpl::bool_<true> {}; template<class Iterator> struct is_iterator<Iterator, typename holder < typename Iterator::iterator_category, typename Iterator::reference, typename Iterator::pointer, typename Iterator::value_type, typename Iterator::difference_type >::type > : boost::mpl::bool_<true> {};
If there is interest, and John Maddock gives the OK, I'll write docs and integrate into type_traits, then submit a pull request.
I definately think it is something very useful to have. Perhaps, it should go in Boost.Iterator instead? -- View this message in context: http://boost.2283326.n4.nabble.com/type-traits-Interest-in-is-iterator-type-... Sent from the Boost - Dev mailing list archive at Nabble.com.
On Aug 19, 2014, at 11:55 AM, Peter Dimov <lists@pdimov.com> wrote:
Beman Dawes wrote:
The intent was always to submit is_iterator to Boost, but we never got around to it. Now I need it in Boost.Filesystem, so I'd rather see it go in type traits than just sticking it into boost/filesystem/detail.
Out of curiosity, why do you need it?
Fwiw, I used something closely related in libc++ for SFINAE on the “member template members” of the containers (the ones taking iterators) to disambiguate them from the members/constructors not taking iterators, but having the same number of parameters. But instead of is_iterator, I needed is_input_iterator, is_forward_iterator, etc. These refinements are implemented similar to what Beman shows, but takes into account which std iterator tag the nested iterator_category type is implicitly convertible to. They can also be used instead of tag dispatching for the selection of an optimal algorithm. Howard
On Tue, Aug 19, 2014 at 12:38 PM, Howard Hinnant <howard.hinnant@gmail.com> wrote:
On Aug 19, 2014, at 11:55 AM, Peter Dimov <lists@pdimov.com> wrote:
Beman Dawes wrote:
The intent was always to submit is_iterator to Boost, but we never got around to it. Now I need it in Boost.Filesystem, so I'd rather see it go in type traits than just sticking it into boost/filesystem/detail.
Out of curiosity, why do you need it?
Fwiw, I used something closely related in libc++ for SFINAE on the “member template members” of the containers (the ones taking iterators) to disambiguate them from the members/constructors not taking iterators, but having the same number of parameters. But instead of is_iterator, I needed is_input_iterator, is_forward_iterator, etc. These refinements are implemented similar to what Beman shows, but takes into account which std iterator tag the nested iterator_category type is implicitly convertible to
Yeah, if we are going ahead with this, is_input_iterator and the rest of the family should be included. --Beman
Fwiw, I used something closely related in libc++ for SFINAE on the “member template members” of the containers (the ones taking iterators) to disambiguate them from the members/constructors not taking iterators, but having the same number of parameters. But instead of is_iterator, I needed is_input_iterator, is_forward_iterator, etc. These refinements are implemented similar to what Beman shows, but takes into account which std iterator tag the nested iterator_category type is implicitly convertible to.
I actually have used a `has_iterator_traversal` trait to check iterator traversal, like this: template<class T, class Traversal, class Enable = void> struct has_iterator_traversal : boost::mpl::bool_<false> {}; template<class T, class Traversal> struct has_iterator_traversal<T, Traversal, typename boost::enable_if<is_iterator<T> >::type> : boost::is_convertible<typename boost::iterator_traversal<T>::type, Traversal>::type {}; It just builds on top of the `is_iterator` trait. It uses the traversal rather than the iterator categories, since they are broken. Paul Fultz II -- View this message in context: http://boost.2283326.n4.nabble.com/type-traits-Interest-in-is-iterator-type-... Sent from the Boost - Dev mailing list archive at Nabble.com.
On 19/08/14 19:09, pfultz2 wrote:
Fwiw, I used something closely related in libc++ for SFINAE on the “member template members” of the containers (the ones taking iterators) to disambiguate them from the members/constructors not taking iterators, but having the same number of parameters. But instead of is_iterator, I needed is_input_iterator, is_forward_iterator, etc. These refinements are implemented similar to what Beman shows, but takes into account which std iterator tag the nested iterator_category type is implicitly convertible to.
I actually have used a `has_iterator_traversal` trait to check iterator traversal, like this:
template<class T, class Traversal, class Enable = void> struct has_iterator_traversal : boost::mpl::bool_<false> {};
template<class T, class Traversal> struct has_iterator_traversal<T, Traversal, typename boost::enable_if<is_iterator<T> >::type> : boost::is_convertible<typename boost::iterator_traversal<T>::type, Traversal>::type {};
Why not just write template<class Iterator> void my_function_impl(Iterator it, std::input_iterator_tag) { ... } template<class Iterator> void my_function(Iterator it) { return my_function_impl(it, typename std::iterator_traits<Iterator>::iterator_category()); } It's simpler and it probably compiles faster too.
Howard Hinnant wrote:
Fwiw, I used something closely related in libc++ for SFINAE on the “member template members” of the containers (the ones taking iterators) to disambiguate them from the members/constructors not taking iterators, but having the same number of parameters. But instead of is_iterator, I needed is_input_iterator, is_forward_iterator, etc.
That was exactly what I had in mind. Instead of enable_if on is_iterator<T>, it's almost always better to use enable_if on is_convertible<typename iterator_traits<T>::iterator_category, tag>, where tag is the kind of iterator the function actually needs. (This requires the new and improved iterator_traits though - were those C++14?) About the only use case I can envisage for the plain is_iterator is template<class It> typename enable_if<is_iterator<T>::value>::type function( It first, It last ) { static_assert( is_forward_iterator<T>::value, "'function' requires forward iterators" ); // ... } where you deliberately don't SFINAE out the function in order to produce a better error message in the presence of other overloads. But even then... is_input_iterator would be good enough, I suppose. (-pedantic on: an iterator is not required to have nested types, so the implementation is not quite correct. Of course, again, without the new and improved iterator_traits, it's the best that we can do.)
The intent was always to submit is_iterator to Boost, but we never got around to it. Now I need it in Boost.Filesystem, so I'd rather see it go in type traits than just sticking it into boost/filesystem/detail.
Out of curiosity, why do you need it?
One situation is inside a constructor call: struct myclass { template <class I> myclass(I a, I b) { // a and b could be iterators, or could be a pair of values, // we need to separate the two cases. } John.
On Aug 19, 2014, at 12:49 PM, John Maddock <boost.regex@virgin.net> wrote:
The intent was always to submit is_iterator to Boost, but we never got around to it. Now I need it in Boost.Filesystem, so I'd rather see it go in type traits than just sticking it into boost/filesystem/detail.
Out of curiosity, why do you need it?
One situation is inside a constructor call:
struct myclass { template <class I> myclass(I a, I b) { // a and b could be iterators, or could be a pair of values, // we need to separate the two cases. }
Additionally in this example (a constructor), tag dispatching doesn’t truly work. One has to remove this constructor from the overload resolution set when I is not an iterator (or the right *kind* of iterator), else is_constructible<myclass, I, I> gets the wrong answer. Howard
On 19/08/14 18:49, John Maddock wrote:
The intent was always to submit is_iterator to Boost, but we never got around to it. Now I need it in Boost.Filesystem, so I'd rather see it go in type traits than just sticking it into boost/filesystem/detail.
Out of curiosity, why do you need it?
One situation is inside a constructor call:
struct myclass { template <class I> myclass(I a, I b) { // a and b could be iterators, or could be a pair of values, // we need to separate the two cases. }
What if your values are iterators?
On Tue, Aug 19, 2014 at 11:55 AM, Peter Dimov <lists@pdimov.com> wrote:
Beman Dawes wrote:
The intent was always to submit is_iterator to Boost, but we never got around to it. Now I need it in Boost.Filesystem, so I'd rather see it go in type traits than just sticking it into boost/filesystem/detail.
Out of curiosity, why do you need it?
Like Howard, for SFINAE. The specific case is implementing the Filesystem TS, which in class path has this: template <class Source> path(const Source& source); Source can be a iterator to a NTCTS or a container. The value_type may be one of 5 char types, and may be the same or different from the value_type of the path. There are six path members templated on Source. I can reduce all that complexity to this implementation for the ctor, and then reused detail::append() to implement the five other member functions. { detail::append(source, m_pathname); } detail::append() has four overloads. enable_if using is_iterator<> distinguishes which overload should be selected. --Beman
On 19/08/14 19:00, Beman Dawes wrote:
On Tue, Aug 19, 2014 at 11:55 AM, Peter Dimov <lists@pdimov.com> wrote:
Beman Dawes wrote:
The intent was always to submit is_iterator to Boost, but we never got around to it. Now I need it in Boost.Filesystem, so I'd rather see it go in type traits than just sticking it into boost/filesystem/detail.
Out of curiosity, why do you need it?
Like Howard, for SFINAE. The specific case is implementing the Filesystem TS, which in class path has this:
template <class Source> path(const Source& source);
Source can be a iterator to a NTCTS or a container. The value_type may be one of 5 char types, and may be the same or different from the value_type of the path. There are six path members templated on Source. I can reduce all that complexity to this implementation for the ctor, and then reused detail::append() to implement the five other member functions.
{ detail::append(source, m_pathname); }
detail::append() has four overloads. enable_if using is_iterator<> distinguishes which overload should be selected.
Why do you want to implement this with SFINAE? SFINAE for many cases scales badly both in terms of code management and scalability. You're better off using overloading or class template (partial) specialization inside the implementation of the library.
On Wed, Aug 20, 2014 at 8:27 AM, Mathias Gaunard < mathias.gaunard@ens-lyon.org> wrote:
On 19/08/14 19:00, Beman Dawes wrote:
On Tue, Aug 19, 2014 at 11:55 AM, Peter Dimov <lists@pdimov.com> wrote:
Beman Dawes wrote:
The intent was always to submit is_iterator to Boost, but we never got
around to it. Now I need it in Boost.Filesystem, so I'd rather see it go in type traits than just sticking it into boost/filesystem/detail.
Out of curiosity, why do you need it?
Like Howard, for SFINAE. The specific case is implementing the Filesystem TS, which in class path has this:
template <class Source> path(const Source& source);
Source can be a iterator to a NTCTS or a container. The value_type may be one of 5 char types, and may be the same or different from the value_type of the path. There are six path members templated on Source. I can reduce all that complexity to this implementation for the ctor, and then reused detail::append() to implement the five other member functions.
{ detail::append(source, m_pathname); }
detail::append() has four overloads. enable_if using is_iterator<> distinguishes which overload should be selected.
Why do you want to implement this with SFINAE?
Good question. The actual constructor needs SFINAE to avoid over-aggressive use as an automatic conversion, so it was on my mind, but there is no reason the implementation needs to use SFINAE.
SFINAE for many cases scales badly both in terms of code management and scalability.
Agreed.
You're better off using overloading or class template (partial) specialization inside the implementation of the library.
That occurred to me yesterday, and I'm in process of doing the implementation with tag dispatched overloads. The code looks much cleaner. But as far as this discussion is concerned, is_iterator (or maybe is_input_iterator) is needed to drive the tag selection, so the question remains whether to add it to type_traits or to filesystem/detail. IFAICS there is enough motivation and interest to make it worth adding to boost.type_traits, but John as maintainer has the final say on that. --Beman
Back in 2011, Howard Hinnant and I put together an is_iterator type trait based on code Howard had developed and used over a period of time.
See http://beman.github.io/is_iterator/is_iterator.hpp for the actual code
See http://beman.github.io/is_iterator/is_iterator_test.cpp for a test program.
The intent was always to submit is_iterator to Boost, but we never got around to it. Now I need it in Boost.Filesystem, so I'd rather see it go in type traits than just sticking it into boost/filesystem/detail.
If there is interest, and John Maddock gives the OK, I'll write docs and integrate into type_traits, then submit a pull request.
I don't see why it shouldn't go in type_traits - in fact I think I've used something similar myself before. John.
On 19/08/14 15:17, Beman Dawes wrote:
Back in 2011, Howard Hinnant and I put together an is_iterator type trait based on code Howard had developed and used over a period of time.
See http://beman.github.io/is_iterator/is_iterator.hpp for the actual code
See http://beman.github.io/is_iterator/is_iterator_test.cpp for a test program.
AFAIK an iterator T doesn't have to provide an iterator_category typedef, only std::iterator_traits<T> does. Therefore this implementation wouldn't work for all iterators.
Mathias Gaunard wrote:
AFAIK an iterator T doesn't have to provide an iterator_category typedef, only std::iterator_traits<T> does.
That's true. However, pre-C++14, iterator_traits<It>::iterator_category causes a hard error (not SFINAE) when It is not an iterator. In C++14, the default iterator_traits<It> is empty when It doesn't have the nested types. So is_iterator<It, tag> :- is_convertible<X::iterator_category, tag> where X is iterator_traits<It> in C++14, It otherwise is the best we can do.
On 20/08/14 14:33, Peter Dimov wrote:
Mathias Gaunard wrote:
AFAIK an iterator T doesn't have to provide an iterator_category typedef, only std::iterator_traits<T> does.
That's true. However, pre-C++14, iterator_traits<It>::iterator_category causes a hard error (not SFINAE) when It is not an iterator. In C++14, the default iterator_traits<It> is empty when It doesn't have the nested types. So
is_iterator<It, tag> :- is_convertible<X::iterator_category, tag> where X is iterator_traits<It> in C++14, It otherwise
is the best we can do.
Alternatively, Boost could provide a boost::iterator_traits that always behaves like C++14's (and would even be an alias to std::iterator_traits in C++14) This would be more clean to me that the code posted above.
Mathias Gaunard wrote:
Alternatively, Boost could provide a boost::iterator_traits that always behaves like C++14's (and would even be an alias to std::iterator_traits in C++14)
Iterators without nested types specialize std::iterator_traits. boost::iterator_traits will not work for them.
On 20/08/2014 15:52, Peter Dimov wrote:
Mathias Gaunard wrote:
Alternatively, Boost could provide a boost::iterator_traits that always behaves like C++14's (and would even be an alias to std::iterator_traits in C++14)
Iterators without nested types specialize std::iterator_traits. boost::iterator_traits will not work for them.
Users that want boost::is_iterator to work on such iterators will need to specialize boost::iterator_traits in addition to std::iterator_traits. Boost could also pre-emptively specialize iterator_traits for well-known iterators that need this.
participants (6)
-
Beman Dawes
-
Howard Hinnant
-
John Maddock
-
Mathias Gaunard
-
Peter Dimov
-
pfultz2