How to check if a template parameter is an iterator type or not?
Asked Answered
G

10

14
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?

Grumpy answered 2/12, 2010 at 14:20 Comment(6)
I guess Concept checking could help you. It's not easy though.Ching
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++?Irs
@Karl Knechtel: My aim is: if type T have operator* and operator++ (including built-in operators), then is_iterator<T>::value will be non-zero.Grumpy
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?Ambit
In addition, the linked question preceded C++11, and that has much better compile time reflection support.Dink
@Grumpy Those requirements are that of the InputIterator concept (en.cppreference.com/w/cpp/concept/InputIterator, see also 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.Rebuttal
D
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 *); 
};
Directrix answered 2/12, 2010 at 14:55 Comment(8)
Why is it necessary to use "|| is_pointer<T>::value"? Shouldn't the first check already be enough given the "template<class R> static void * test(R *); // Pointer" line?Amontillado
@Jan de Vos. I do not know why Armen changed my answer.Directrix
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.Ambit
"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 :-)Ambit
Any C++03-compatible version of this? ie. without iterator_category and variadic functions?Mustachio
@metamorphosis, the code is valid C++03 (it doesn't correctly identify all iterators, but it's valid C++03).Cyrillic
@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?Mustachio
@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.Cyrillic
S
22

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

Spectrogram answered 20/8, 2012 at 6:8 Comment(6)
Try it with a random struct like Foo (I tried it on Visual C++). Same problem as mine.Puccini
Visual Studio's rules are not the most similar. You will have to produce something compiler-specific.Fellah
Well, it does fail on Visual C++, so either the compiler or this is wrong...Puccini
@DeadMG: Do you mean it's a bug? or that there's no portable way to do it?Puccini
@Mehrdad: There's a portable solution and a VC++ solution. Together that should be sufficient.Dink
how does this work? for example using Tp = typename std::enable_if<!std::is_same<typename std::iterator_traits<It>::value_type, void>::value>::type; Tp = voidMalkamalkah
R
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.

Recalescence answered 14/7, 2015 at 14:28 Comment(3)
In addition to being incrementable and dereferencable, valid iterators are required to ensure that iterator_traits<T> defines value_type, difference_type etc. (either by providing them as nested types in the iterator or by specializing iterator_traits). Since DR 2408 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<T>::iterator_category to your void_t argument list.Cyrillic
@JonathanWakely Might as well just check that one right, instead of everything else?Recalescence
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 :)Cyrillic
D
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 *); 
};
Directrix answered 2/12, 2010 at 14:55 Comment(8)
Why is it necessary to use "|| is_pointer<T>::value"? Shouldn't the first check already be enough given the "template<class R> static void * test(R *); // Pointer" line?Amontillado
@Jan de Vos. I do not know why Armen changed my answer.Directrix
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.Ambit
"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 :-)Ambit
Any C++03-compatible version of this? ie. without iterator_category and variadic functions?Mustachio
@metamorphosis, the code is valid C++03 (it doesn't correctly identify all iterators, but it's valid C++03).Cyrillic
@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?Mustachio
@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.Cyrillic
C
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.

Campagna answered 10/6, 2015 at 20:10 Comment(0)
O
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.

Oreste answered 2/12, 2010 at 14:24 Comment(9)
I have tried for several hours and found it is not a trivial problem at least in VC++ 2010.Grumpy
It is not trivial, but it's doableOreste
@Armen Tsirunyan: I tried in many ways and don't succeed. Please help meGrumpy
@Armen Tsirunyan: I want to use iterator_traits to do it, but it is too hard for me. Maybe I should use your method.Grumpy
Do standard iterators have to have the nested typdef? Can't the implementation legally just specialized std::iterator_traits directly instead?Ambit
@Steve: I may be wrong, but I believe the standard iterators are required to have the nested typedef!Oreste
@Armen: any idea where? I thought that vector<T>::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.Ambit
This answer is wrong. No iterators are ever required to provide a nested type, and pointers cannot do so.Cyrillic
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 en.cppreference.com/w/cpp/iterator/… 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."Rebuttal
P
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.

Pronuba answered 20/8, 2012 at 6:19 Comment(2)
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...Puccini
@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.Pronuba
B
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>; };
Balladist answered 27/4, 2022 at 5:51 Comment(1)
Examples of using these concepts in a template are: (1) template <typename IT> requires std::input_iterator(IT) void func(IT i) { std::cout << *i; } , (2) template <typename IT, typename ValT> requires std::output_iterator<IT, ValT> void func(IT i, const ValT& val) { *i = val; }Tenebrae
R
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.)

Rebuttal answered 24/10, 2016 at 20:5 Comment(0)
D
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

Dickie answered 3/6, 2021 at 13:15 Comment(0)
C
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 { };
Cantlon answered 18/7, 2012 at 6:51 Comment(1)
Test does not work if T is not dereferencable ie. a int or similarMustachio

© 2022 - 2024 — McMap. All rights reserved.