11
  • I'm trying to code a is_iterator<T> type trait. Where when T is an iterator type is_iterator<T>::value == true otherwise is is_iterator<T>::value == false.

  • What I tried so far:


template <class, class Enable = void> 
struct is_iterator : std::false_type {};

template <typename T> 
struct is_iterator<T, typename std::enable_if<std::is_pointer<typename
     std::iterator_traits<T>::pointer>::value>::type> : std::true_type {};

LIVE DEMO


Q: Is there a more proper way to define a is_iterator type trait than the one displayed above?

101010
  • 41,839
  • 11
  • 94
  • 168
  • Why does this need to be a whole class or struct? Why not have `bool is_iterator(T)`? – scohe001 Aug 13 '14 at 15:58
  • @Josh for use in SFINAE. – 101010 Aug 13 '14 at 15:58
  • You could check for all the requirements of the iterator concept: http://en.cppreference.com/w/cpp/concept/Iterator. – Drax Aug 13 '14 at 16:12
  • 3
    N.B. pre-[DR 2408](http://cplusplus.github.io/LWG/lwg-active.html#2408) `iterator_traits` is not SFINAE-friendly and instantiating it for a non-iterator type is a hard error, not a substitution failure – Jonathan Wakely Aug 13 '14 at 17:20
  • 3
    A type is an iterator if it is either an input iterator or an output iterator. Defining `is_input_iterator` and `is_output_iterator` is left as an exercise for the reader. :-) – Howard Hinnant Aug 13 '14 at 23:08

3 Answers3

8

As I said in comments, the solutions presented here rely on non-portable properties of iterators_traits in some implementations. According to the C++03 and C++11 standards, iterator_traits is only defined for iterators (and the special case of pointers) so any other use is undefined behaviour. Specifically, using iterator_traits<T>::pointer in a SFINAE context isn't guaranteed to work, because instantiating iterator_traits<T> will refer to T::value_type, T::pointer, T::iterator_category etc. and that happens outside the "immediate context" where SFINAE doesn't apply.

C++14 will fix that was supposed to fix that (it happened post-C++14 with DR 2408), but for C++11 the safe way to define is_iterator is to write a trait that checks for all the required operations an iterator must define. The only operations that all iterators are required to support are operator* and pre- and post-increment. Unfortunately, there can be types that define those operations which are not valid iterators, so writing a correct trait is quite hard.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Jonathan, does your answer also apply to the propositions at [this question](http://stackoverflow.com/q/4335962/1593077)? Or is one of those a good enough "trait that checks for all the required operations"? Also, if a type satisfies all requirements of the iterator concept (which supposedly can be checked) - is it not ipso facto an iterator, albeit accidentally so? – einpoklum Oct 16 '16 at 19:59
  • Most of those answers are wrong (because they assume iterators will have particular nested typedefs), but Barry's answer is probably good enough. Yes, if a type satisfies all the requirements of a concept then it is a model of that concept. – Jonathan Wakely Oct 16 '16 at 23:03
  • @JonathanWakely Sorry for picking up an old question. You said "Barry's answer is probably good enough". Unfortunately I don't see a Barry. Did you refer to the answer with the time stamp Aug 13 '14 at 16:26 (currently by the username Praetorian)? – C. Binair Feb 16 '21 at 16:08
  • @C.Binair I was replying to a comment about a different question, and so I was talking about an answer to ***that*** question, i.e. [this answer](https://stackoverflow.com/a/31409532/981959). – Jonathan Wakely Feb 17 '21 at 17:43
5

Your check fails if std::iterator_traits<T>::pointer is a type that is not a pointer, for instance if T = std::ostream_iterator<U>.

I think a better test might be whether std::iterator_traits<T>::iterator_category is either std::input_iterator_tag or a derived type, or std::output_iterator_tag.

template <class, class Enable = void> struct is_iterator : std::false_type {};
template <typename T> 
struct is_iterator
<T, 
 typename std::enable_if<
    std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<T>::iterator_category>::value ||
    std::is_same<std::output_iterator_tag, typename std::iterator_traits<T>::iterator_category>::value 
 >::type> 
 : std::true_type {};
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • what about the other iterator tags? random, bidirectional etc –  Jan 04 '18 at 10:28
  • @IonTodirel All input iterator categories are derived from `input_iterator_tag`, see [this](http://en.cppreference.com/w/cpp/iterator/iterator_tags) – Praetorian Jan 04 '18 at 17:47
4

I think there's no need to check for any particular property of iterator_traits's nested typedefs. It should be enough to check for mere presence of iterator_traits<T>::value_type (or any other nested typedef, for that matter), because every iterator has one.

#include <type_traits>
#include <iostream>
#include <iterator>

template<typename>
struct void_ {
  typedef void type;
};
// remove typename spam below:
template<typename Discard>
using void_t=typename void_<Discard>::type;
template<typename T>
using decay_t=typename std::decay<T>::type;    

// stick helper types into details, so the interface
// for is_iterator is cleaner:
namespace details {
  template<typename T, typename Enable=void>
  sturct is_iterator : is_iterator2<T, Enable> {};

  // special case: void* is not an iterator
  // but T* specialization would pick it up
  // if there weren't the following:

  template<typename V>
  struct is_iterator<V*, decay_t<V>> : std::false_type {};

  // phase 2: SFINAE pass to std::iterator_traits test
  // valid in C++14, and in many C++11 compilers, except
  // for above void issue:
  template<typename, typename Enable = void>
  struct is_iterator2 : std::false_type {};

  template<typename T>
  struct is_iterator2<T, 
    void_t< typename std::iterator_traits<T>::value_type>
  > : std::true_type {};
}
template<typename T>
struct is_iterator : details::is_iterator<T> {};

int main()
{
    std::cout
        << is_iterator<int*>::value
        << is_iterator<double>::value;
}

Unfortunately this isn't guaranteed to work in C++11, but C++14 will fix it (thanks to Jonathan Wakely for pointing it out).

Community
  • 1
  • 1
jrok
  • 54,456
  • 9
  • 109
  • 141
  • 4
    But `iterator_traits` is not SFINAE-friendly in C++11, so instantiating `iterator_traits` for a non-iterator type is undefined behaviour, and the error is not in the immediate context, so is an error not a substitution failure (http://cplusplus.github.io/LWG/lwg-active.html#2408 addresses that) – Jonathan Wakely Aug 13 '14 at 17:19
  • 1
    Luckily some implementations have been shipping a SFINAE-friendly `iterator_traits` for years, because it's a lot more useful that way :) – Jonathan Wakely Aug 13 '14 at 17:28
  • 1
    Many almost-SFINAE `iterator_traits` will fail horribly on `iterator_traits` (and cv variants). (The `T*` specialization will catch it, but the instance will fail to compile) – Yakk - Adam Nevraumont Aug 19 '14 at 13:30
  • Sadly, `void const*` `void volatile*` `void const volatile*` probably also break. – Yakk - Adam Nevraumont Aug 19 '14 at 13:57