13
template<class T>
struct is_iterator
{
    static const bool value = ??? // What to write ???
};

int main()
{
    assert(false == is_iterator<int>::value);
    assert(true == is_iterator<vector<int>::iterator>::value);
    assert(true == is_iterator<list<int>::iterator>::value);
    assert(true == is_iterator<string::iterator>::value);
    assert(true == is_iterator<char*>::value); // a raw pointer is also an iterator
}

The question is: How to make the five assert statements pass?

Barry
  • 286,269
  • 29
  • 621
  • 977
xmllmx
  • 39,765
  • 26
  • 162
  • 323
  • I guess Concept checking could help you. It's not easy though. – Matthieu M. Dec 02 '10 at 14:24
  • Do you really **need** to know? What are you going to do when you find out? Would it be sufficient to just test that the type implements, say, operator* and operator++? – Karl Knechtel Dec 02 '10 at 14:45
  • @Karl Knechtel: My aim is: if type T have operator* and operator++ (including built-in operators), then is_iterator::value will be non-zero. – xmllmx Dec 02 '10 at 15:00
  • now you've said two different things. Do you only want to test for the presence of those two operators, or do you want to test whether it's an iterator? – Steve Jessop Dec 02 '10 at 17:43
  • In addition, the linked question preceded C++11, and that has much better compile time reflection support. – MSalters Aug 20 '12 at 09:27
  • @xmllmx Those requirements are that of the InputIterator concept (http://en.cppreference.com/w/cpp/concept/InputIterator, see also http://en.cppreference.com/w/cpp/iterator) for which the standard provides a traits class to check for standard iterators. Other conforming iterators should specialize (or derive from) the type traits accordingly. – mabraham Oct 24 '16 at 19:53

10 Answers10

20

How about something like this?

template<typename T, typename = void>
struct is_iterator
{
   static constexpr bool value = false;
};

template<typename T>
struct is_iterator<T, typename std::enable_if<!std::is_same<typename std::iterator_traits<T>::value_type, void>::value>::type>
{
   static constexpr bool value = true;
};

example:

#include <iostream>
#include <type_traits>
#include <vector>

template<typename T, typename = void>
struct is_iterator
{
   static constexpr bool value = false;
};

template<typename T>
struct is_iterator<T, typename std::enable_if<!std::is_same<typename std::iterator_traits<T>::value_type, void>::value>::type>
{
   static constexpr bool value = true;
};

int main()
{
   static_assert(!is_iterator<int>::value);
   static_assert(is_iterator<int*>::value);
   static_assert(is_iterator<std::vector<int>::iterator>::value);
}

http://liveworkspace.org/code/7dcf96c97fd0b7a69f12658fc7b2693e

425nesp
  • 6,936
  • 9
  • 50
  • 61
ForEveR
  • 55,233
  • 2
  • 119
  • 133
17

Coming in here a few years later, where C++11 and C++14 make it a lot easier to do such things. An iterator is, at its core, something that is dereferencable, incrementable. If it's an input iterator, then also comparable. Let's go with the latter - since that looks like what you want.

The simplest version would be to use void_t:

template <typename... >
using void_t = void;

Base case:

template <typename T, typename = void>
struct is_input_iterator : std::false_type { };

Valid case specialization:

template <typename T>
struct is_input_iterator<T,
    void_t<decltype(++std::declval<T&>()),                       // incrementable,
           decltype(*std::declval<T&>()),                        // dereferencable,
           decltype(std::declval<T&>() == std::declval<T&>())>>  // comparable
    : std::true_type { };

Alias:

template <typename T>
using is_input_iterator_t = typename is_input_iterator<T>::type;

No need to rely on iterator_category or using the tedious C++03 style of check things using overload resolution. Expression SFINAE is where it's at.


As Mr. Wakely points out in the comments, [iterator.traits] requires that:

it is required that if Iterator is the type of an iterator, the types

iterator_traits<Iterator>::difference_type
iterator_traits<Iterator>::value_type
iterator_traits<Iterator>::iterator_category

be defined as the iterator’s difference type, value type and iterator category, respectively.

So we can define our iterator trait to simply check for that:

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

template <class T>
struct is_iterator<T, void_t<
    typename std::iterator_traits<T>::iterator_category
>> : std::true_type { };

If iterator_traits<T>::iterator_category is ill-formed, then T is not an iterator.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    In addition to being incrementable and dereferencable, valid iterators are required to ensure that `iterator_traits` defines `value_type`, `difference_type` etc. (either by providing them as nested types in the iterator or by specializing `iterator_traits`). Since [DR 2408](http://wg21.link/lwg2408) you can safely check for any one of them in an expression SFINAE context to tell if `iterator_traits` thinks the type is an iterator. So I'd add `iterator_traits::iterator_category` to your `void_t` argument list. – Jonathan Wakely Oct 16 '16 at 23:13
  • @JonathanWakely Might as well just check that one right, instead of everything else? – Barry Oct 17 '16 at 12:28
  • Yes, it's probably safe to assume a type won't provide all the nested types (or a specialization of `iterator_traits`) without also being incrementable or dereferencable. If a type does do that you can slap the author and refuse to use the type, rather than try to make your trait cope with it :) – Jonathan Wakely Oct 17 '16 at 14:33
5
template<class T>
struct is_iterator
{   
    static T makeT();
    typedef void * twoptrs[2];  // sizeof(twoptrs) > sizeof(void *)
    static twoptrs & test(...); // Common case
    template<class R> static typename R::iterator_category * test(R); // Iterator
    template<class R> static void * test(R *); // Pointer

    static const bool value = sizeof(test(makeT())) == sizeof(void *); 
};
Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
Alexey Malistov
  • 26,407
  • 13
  • 68
  • 88
  • Why is it necessary to use "|| is_pointer::value"? Shouldn't the first check already be enough given the "template static void * test(R *); // Pointer" line? – Jan de Vos Dec 02 '10 at 15:48
  • @Jan de Vos. I do not know why Armen changed my answer. – Alexey Malistov Dec 02 '10 at 15:56
  • 3
    Fails to compile for a few things that aren't iterators (for example function types), and returns true for a few things that aren't iterators (for example pointer-to-function: there's no such thing as an array of functions and you can't increment a pointer-to-function). Close enough for most practical purposes though, I think. I *think* (not sure) it can also return false for some things that are iterators, but are neither pointers nor have an `iterator_type` typedef. Iterators in `std` are allowed just to specialize `std::iterator_traits`, I think. – Steve Jessop Dec 02 '10 at 18:00
  • 1
    "returns true for a few things that aren't iterators" - but then, `std::iterator_traits` gives a false match for function pointers too, at least in my implementation, so I definitely forgive you that :-) – Steve Jessop Dec 02 '10 at 18:07
  • Any C++03-compatible version of this? ie. without iterator_category and variadic functions? – metamorphosis Jun 15 '16 at 00:16
  • @metamorphosis, the code is valid C++03 (it doesn't correctly identify all iterators, but it's valid C++03). – Jonathan Wakely Oct 16 '16 at 23:04
  • @JonathanWakely I didn't pick up on iterator_category being C++03, which it is, but where they've written "...", are they just basically writing "etc" ie. as a placeholder, not a variadic argument? – metamorphosis Oct 23 '16 at 01:14
  • @metamorphosis, no, it's valid C++ code, but it's not a variadic template it's a varargs function like `printf`, which has been valid in C since before C++ even existed. It's valid C++03 code. – Jonathan Wakely Oct 23 '16 at 12:44
4

I believe this should be a complete solution. Try it on http://gcc.godbolt.org and see the resulting assembly for the test functions.

#include <type_traits>
#include <iterator>
#include <vector>
#include <utility>

template <typename T>
  struct is_iterator {
  static char test(...);

  template <typename U,
    typename=typename std::iterator_traits<U>::difference_type,
    typename=typename std::iterator_traits<U>::pointer,
    typename=typename std::iterator_traits<U>::reference,
    typename=typename std::iterator_traits<U>::value_type,
    typename=typename std::iterator_traits<U>::iterator_category
  > static long test(U&&);

  constexpr static bool value = std::is_same<decltype(test(std::declval<T>())),long>::value;
};

struct Foo {};

//Returns true
bool f() { return is_iterator<typename std::vector<int>::iterator>::value; }
//Returns true    
bool fc() { return is_iterator<typename std::vector<int>::const_iterator>::value; }
//Returns true
bool fr() { return is_iterator<typename std::vector<int>::reverse_iterator>::value; }
//Returns true
bool fcr() { return is_iterator<typename std::vector<int>::const_reverse_iterator>::value; }
//Returns true
bool g() { return is_iterator<int*>::value; }
//Returns true
bool gc() { return is_iterator<const int*>::value; }
//Returns false
bool h() { return is_iterator<int>::value; }
//Returns false
bool i() { return is_iterator<Foo>::value; }

This implementation uses SFINAE and overloading precedence. test(U&&) always has higher precedence than test(...) so it will always be chosen if not removed by SFINAE.

For an iterator type T, std::iterator_traits<T> has all of the above mentioned typedefs present so test(U&&) and test(...) are both overload candidates. Since test(U&&) has higher precedence, its always chosen.

For a non-iterator type T, test(U&&) fails SFINAE because std::iterator_traits<T> does not have the nested typedefs. Therefore the only remaining candidate is test(...).

Note that this trait will also fail if someone specializes std::iterator_traits<T> for some type T and does not provide all of the required typedefs.

MikeMB
  • 20,029
  • 9
  • 57
  • 102
Matthew Fioravante
  • 1,478
  • 15
  • 19
3

I implemented this one some time ago:

template <typename T>
struct is_iterator {  
    template <typename U>
    static char test(typename std::iterator_traits<U>::pointer* x);

    template <typename U>
    static long test(U* x);

    static const bool value = sizeof(test<T>(nullptr)) == 1;
};

It compiles fine using your example. I can't test it on VC though.

Demo here.

mfontanini
  • 21,410
  • 4
  • 65
  • 73
  • 1
    Seems like VC is the problem, because it gives the same error as before on it. Do you know if it's legal to refer to a nonexistent type like that and expect SFINAE? If it is then I'll make a separate solution for VC, but if it just happens to be a quirk of GCC or something then I'm not sure... – user541686 Aug 20 '12 at 06:27
  • @Mehrdad I don't see how that couldn't end up in SFINAE. The substitution will fail when using `Foo`. It surely is a problem with VC. This compiles with both gcc and clang. – mfontanini Aug 20 '12 at 06:32
3

Well, you could check for the type to have a nested typedef called iterator_category This can be done using SFINAE, and the exact technique can be found in wiki page for SFINAE. This isn't a 100% method, but all decent iterators should provide the common typedefs for iterators, and the iterator_category is one that is unique to iterators. Also don't forget to check if TYPE is simply a pointer. Pointers are iterators.

Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • I have tried for several hours and found it is not a trivial problem at least in VC++ 2010. – xmllmx Dec 02 '10 at 14:25
  • It is not trivial, but it's doable – Armen Tsirunyan Dec 02 '10 at 14:26
  • @Armen Tsirunyan: I tried in many ways and don't succeed. Please help me – xmllmx Dec 02 '10 at 14:30
  • @Armen Tsirunyan: I want to use iterator_traits to do it, but it is too hard for me. Maybe I should use your method. – xmllmx Dec 02 '10 at 14:44
  • 1
    Do standard iterators have to have the nested typdef? Can't the implementation legally just specialized `std::iterator_traits` directly instead? – Steve Jessop Dec 02 '10 at 18:05
  • 1
    @Steve: I may be wrong, but I believe the standard iterators **are** required to have the nested typedef! – Armen Tsirunyan Dec 02 '10 at 21:15
  • 2
    @Armen: any idea where? I thought that `vector::iterator` is allowed to be `T*`, and hence certainly doesn't have the nested typedefs (although it will still pass your test). Anyway, what I didn't think of when writing it is that non-standard iterators can specialize `std::iterator_traits` too, although it's easier to just inherit from `std::iterator`. 24.3.1 just says iterators must have iterator_traits, nothing about typedefs. – Steve Jessop Dec 02 '10 at 21:34
  • 2
    This answer is wrong. No iterators are ever required to provide a nested type, and pointers cannot do so. – Jonathan Wakely Oct 16 '16 at 22:58
  • Agree the answer is literally wrong - an iterator *traits* class must provide that typedef, and that implementation decision exists precisely because a pointer cannot have a nested typedef. See http://en.cppreference.com/w/cpp/iterator/iterator_traits#Specializations where it says "This type trait may be specialized for user-provided types that may be used as iterators. The standard library provides two partial specializations for pointer types T*, which makes it possible to use all iterator-based algorithms with raw pointers." – mabraham Oct 24 '16 at 19:47
3

Since C++20:

The proper way to detect any iterator would be with concepts, namely, std::input_or_output_iterator.

Quoting cppreference:

template <class I>
concept input_or_output_iterator =
requires(I i) {
  { *i } -> /*can-reference*/;
} &&
std::weakly_incrementable<I>;

The input_or_output_iterator concept forms the basis of the iterator concept taxonomy; every iterator type satisfies the input_or_output_iterator requirements.

Note that the exposition only type /*can-reference*/ really just means not void, and can be accomplished like the following:

template <class T>
using with_ref_t = T &;

template <class T>
concept can_reference = requires() { typename detail::with_ref_t<T>; };
Hunter Kohler
  • 1,885
  • 1
  • 18
  • 23
  • Examples of using these concepts in a template are: (1) `template requires std::input_iterator(IT) void func(IT i) { std::cout << *i; }` , (2) `template requires std::output_iterator void func(IT i, const ValT& val) { *i = val; }` – Reed Hedges Jul 02 '23 at 21:07
2

The original poster clarified that they are actually asking for a way to identify an InputIterator (see http://en.cppreference.com/w/cpp/concept/InputIterator) because they want to be able to increment and dereference the iterator. This has a very simple SFINAE solution in standard C++11, e.g. similar to that from the gcc STL:

template<typename InputIterator>
using RequireInputIterator = typename
    std::enable_if<std::is_convertible<typename
                                       std::iterator_traits<InputIterator>::iterator_category,
                                       std::input_iterator_tag>::value>::type;

...

// Example: declare a vector constructor from a pair of input iterators.
template <typename InputIterator, typename = RequireInputIterator<InputIterator> >
    MyVector(InputIterator first, InputIterator last) { /* ... */ };

This relies on the iterator type traits classes, which define the typedefs that Armen Tsirunyan thought were required of the iterators themselves. (The iterators can provide those typedefs, but they can also provide them in traits classes, which is necessary in order to use naked pointers as iterators, and the standard library implementations are required to do so.)

mabraham
  • 2,806
  • 3
  • 28
  • 25
2

Nothing new but a C++17 way of doing it :

#include <type_traits>

// default case
template <class T, class = void>
struct is_iterator : std::false_type
{
};


// specialization
template <class T>
struct is_iterator<T,
                   std::void_t<typename std::iterator_traits<T>::difference_type,
                               typename std::iterator_traits<T>::pointer,
                               typename std::iterator_traits<T>::reference,
                               typename std::iterator_traits<T>::value_type,
                               typename std::iterator_traits<T>::iterator_category>> : std::true_type
{
};

template <class T>
constexpr bool is_iterator_v = is_iterator<T>::value;

some tests:

#include <vector>
#include <list>
#include <map>
static_assert(is_iterator_v<std::vector<int>::iterator>);
static_assert(is_iterator_v<std::list<double>::const_iterator>);
static_assert(is_iterator_v<int*>);
static_assert(!is_iterator_v<std::list<double>>);
static_assert(!is_iterator_v<int>);

How it works:

  1. Some background :
  • std::false_type::value == false
  • std::true_type::value == true
  • std::void_t<X> <=> void if X is a valid type. If not it will cause a substitution Failure
  • is_iterator<X> is seen as is_iterator<X, void>
  • If a specialization matches it will be used
  1. Detail:

If T is an iterator then these types exist:

std::iterator_traits<T>::difference_type
std::iterator_traits<T>::pointer
std::iterator_traits<T>::reference
std::iterator_traits<T>::value_type
std::iterator_traits<T>::iterator_category

So std::void_t<...> is void.

The specialization match is_iterator<T,void> (and also is_iterator<T>) and inherit of std::true_type

If T is not an iterator then at least one of the previous type doesn't exist, so std::void_t<...> doesn't name a type and the whole specialization is an substitution Failure. So the only match for is_iterator is the default case whom inherit of std::false_type

Martin Morterol
  • 2,560
  • 1
  • 10
  • 15
1
template < class T, class Enabler = void >
struct is_iterator : public boost::false_type { };

template < class T >
struct is_iterator< T, typename boost::enable_if_c<
        sizeof(*(*(T*)0)) + sizeof((*(T*)0)++) + sizeof(++(*(T*)0)) +
        sizeof((*(T*)0) == (*(T*)0)) + sizeof((*(T*)0) != (*(T*)0)) +
        sizeof((*(T*)0) = (*(T*)0)) >::type > : public boost::true_type { };