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?
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.
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 {};
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).