Подтвердить что ты не робот

Как проверить, является ли произвольный тип итератором?

Это точный дубликат этого вопроса, за исключением того, что принятый ответ неверен, поэтому я снова спрашиваю его:

Как вы правильно проверяете, является ли данный тип T итератором?

Моя попытка решить это:

// Assume enable_if and is_same are defined
// (hoping for a solution that works for C++03 too)

template<class T>
class is_iterator
{
    static char (&test(...))[2];
    template<class U>
    static typename std::enable_if<
        !std::is_same<
            typename std::iterator_traits<T>::value_type,
            void
        >::value,
        char
    >::type test(U);
public:
    static bool const value = sizeof(test(0)) == 1;
};

struct Foo { };

int main()
{
    return is_iterator<Foo>::value;
}

на Visual С++ происходит сбой:

...\vc\include\xutility(373):
ошибка C2039: 'iterator_category': не является членом 'Foo'

потому что iterator_traits ищет определение value_type в Foo, которое (очевидно) не существует.

I am знает, что __if_exists является возможностью на Visual С++, но я ищу портативное решение.

4b9b3361

Ответ 1

Как насчет чего-то подобного?

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

Пример:

#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, "ass");
   static_assert(is_iterator<int*>::value, "ass");
   static_assert(is_iterator<std::vector<int>::iterator>::value, "ass");
}

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

Ответ 2

Я реализовал это еще некоторое время назад:

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

Он отлично компилируется с использованием вашего примера. Я не могу проверить его на VC.

Демо здесь.

Ответ 3

Я считаю, что это должно быть полным решением. Попробуйте на http://gcc.godbolt.org и посмотрите результирующую сборку для тестовых функций.

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

В этой реализации используется приоритет SFINAE и перегрузки. test(U&&) всегда имеет более высокий приоритет, чем test(...), поэтому он всегда будет выбран, если не удалить SFINAE.

Для типа итератора T, std::iterator_traits<T> присутствует все вышеупомянутые typedefs, поэтому test(U&&) и test(...) являются одновременно кандидатами перегрузки. Так как test(U&&) имеет более высокий приоритет, он всегда выбирается.

Для типа нетератора T, test(U&&) не работает SFINAE, потому что std::iterator_traits<T> не имеет вложенных typedef. Поэтому единственным оставшимся кандидатом является test(...).

Обратите внимание, что этот признак также потерпит неудачу, если кто-то специализируется на std::iterator_traits<T> для некоторого типа T и не предоставляет все необходимые typedefs.