xmllmx
xmllmx

Reputation: 42371

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

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?

Upvotes: 14

Views: 10304

Answers (10)

Hunter Kohler
Hunter Kohler

Reputation: 2725

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>; };

Upvotes: 3

ForEveR
ForEveR

Reputation: 55897

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

Upvotes: 22

Martin Morterol
Martin Morterol

Reputation: 2870

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

Upvotes: 2

mabraham
mabraham

Reputation: 3016

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.)

Upvotes: 2

Barry
Barry

Reputation: 303216

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.

Upvotes: 18

Matthew Fioravante
Matthew Fioravante

Reputation: 1498

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.

Upvotes: 4

mfontanini
mfontanini

Reputation: 21900

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.

Upvotes: 3

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 { };

Upvotes: 1

Alexey Malistov
Alexey Malistov

Reputation: 26985

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 *); 
};

Upvotes: 5

Armen Tsirunyan
Armen Tsirunyan

Reputation: 133044

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.

Upvotes: 3

Related Questions