【问题标题】:How to check if an arbitrary type is an iterator?如何检查任意类型是否是迭代器?
【发布时间】:2012-08-15 11:52:20
【问题描述】:

这是this question 的完全相同的副本,除了接受的答案是错误的,所以我再问一遍:

您如何正确检查给定类型 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 C++ 上碰巧失败了:

...\vc\include\xutility(373):
错误 C2039:'iterator_category':不是'Foo' 的成员

因为iterator_traits 正在Foo 中寻找value_type 的定义,而该定义(显然)不存在。

知道 __if_exists 在 Visual C++ 上是一种可能性,但我正在寻找一种可移植的解决方案。

【问题讨论】:

  • 另外,链接的问题早于 C++11,并且有更好的编译时反射支持。

标签: c++ iterator


【解决方案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

【讨论】:

  • 尝试使用像Foo 这样的随机结构(我在 Visual C++ 上尝试过)。和我一样的问题。
  • Visual Studio 的规则并不是最相似的。你将不得不产生一些编译器特定的东西。
  • 嗯,它在 Visual C++ 上确实失败了,所以要么是编译器,要么这是错误的......
  • @DeadMG:你的意思是这是一个错误吗?还是没有便携的方法可以做到这一点?
  • @Mehrdad:有一个可移植的解决方案和一个 VC++ 解决方案。加起来就足够了。
【解决方案2】:

我相信这应该是一个完整的解决方案。在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&amp;&amp;) 的优先级始终高于 test(...),因此如果未被 SFINAE 删除,它将始终被选中。

对于迭代器类型Tstd::iterator_traits&lt;T&gt; 具有上述所有类型定义,因此test(U&amp;&amp;)test(...) 都是重载候选者。由于test(U&amp;&amp;) 具有更高的优先级,因此始终选择它。

对于非迭代器类型 Ttest(U&amp;&amp;) 会导致 SFINAE 失败,因为 std::iterator_traits&lt;T&gt; 没有嵌套的 typedef。因此,唯一剩下的候选人是test(...)

请注意,如果有人将 std::iterator_traits&lt;T&gt; 专门用于某些类型 T 并且未提供所有必需的 typedef,则此 trait 也会失败。

【讨论】:

    【解决方案3】:

    我前段时间实现了这个:

    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 上测试它。

    演示here.

    【讨论】:

    • 似乎 VC 是问题所在,因为它给出了与以前相同的错误。你知道引用这样一个不存在的类型并期待 SFINAE 是否合法?如果是,那么我将为 VC 制定一个单独的解决方案,但如果它恰好是 GCC 的怪癖或其他什么,那么我不确定......
    • @Mehrdad 我看不出这怎么会出现在 SFINAE 中。使用Foo 时替换将失败。这肯定是VC的问题。这与 gcc 和 clang 一起编译。
    【解决方案4】:

    除了 C++17 的做法之外没有什么新东西:

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

    一些测试:

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

    它是如何工作的:

    1. 一些背景:
    • std::false_type::value == false
    • std::true_type::value == true
    • std::void_t&lt;X&gt; &lt;=&gt; void 如果 X 是有效类型。否则会导致替换失败
    • is_iterator&lt;X&gt; 被视为 is_iterator&lt;X, void&gt;
    • 如果专业化匹配,它将被使用
    1. 详情:

    如果T 是一个迭代器,那么这些类型存在:

    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
    

    所以std::void_t&lt;...&gt;void

    专业化匹配is_iterator&lt;T,void&gt;(以及is_iterator&lt;T&gt;)并继承std::true_type

    如果T 不是不是 迭代器,那么至少有一个先前的类型不存在,所以std::void_t&lt;...&gt; 没有命名类型并且整个特化是替换失败。所以is_iterator的唯一匹配是继承std::false_type的默认情况

    【讨论】:

      猜你喜欢
      • 2016-05-26
      • 1970-01-01
      • 2023-04-10
      • 1970-01-01
      • 2011-07-22
      • 2018-05-16
      • 1970-01-01
      • 2017-06-05
      • 2011-01-04
      相关资源
      最近更新 更多