【问题标题】:specializing iterator_traits特化 iterator_traits
【发布时间】:2011-12-17 04:26:44
【问题描述】:

我想将std::iterator_traits<> 专门用于容器类模板的迭代器,该容器类模板 具有通常的嵌套类型定义(如value_typedifference_type 等)及其来源我不应该修改。基本上我想做这样的事情:

template <typename T> struct iterator_traits<typename Container<T>::iterator> 
{
    typedef T value_type; 
    //  etc.
}; 

除了这不起作用,因为编译器无法从Container&lt;T&gt;::iterator 推断出T

有没有什么可行的方法来达到同样的效果?


例如:

template <typename T>
class SomeContainerFromAThirdPartyLib
{
    typedef T ValueType;    //  not value_type! 
    //  no difference_type

    class iterator
    {
        typedef T ValueType;    //  not value_type! 
        //  no difference_type  
        ...
    }; 
    iterator begin() { ... }
    iterator end() { ... }
    ...
}; 

现在假设我使用此类的实例调用std::count()。据我所知,在大多数 STL 实现中,count() 返回iterator_traits&lt;Iterator&gt;::difference_typeiterator_traits&lt;I&gt; 的主要模板只做typedef typename I::difference_type difference_type。与其他嵌套类型相同。

现在在我们的示例中这显然不起作用,因为没有Container::iterator::difference_type。我想我可以在不修改迭代器类的情况下解决这个问题,方法是将iterator_traits 专门用于任何Container&lt;T&gt; 的迭代器。

最后,我只是希望能够使用标准算法,如计数、查找、排序等,最好不修改任何现有代码。我认为iterator_traits 的全部意义在于:能够为不支持内置的迭代器类型指定类型(如value_typediff_type 等)。不幸的是,我不知道如何为Container&lt;T&gt; 的所有实例专门化特征类。

【问题讨论】:

  • Container 是在哪里声明的?还是任何容器?
  • 任何破坏 stl 支持的容器:它确实有迭代器和 const_iterator,它们可以递增、递减、取消引用等,但容器和迭代器都没有符合 std 的嵌套 typedef。
  • 我仍然没有完全理解这个问题。您能否更新问题,并在您的问题中举例说明您将如何使用它?我的意思是iterator_traits&lt;T&gt; 什么时候应该使用默认类,什么时候应该使用专用版本?
  • @iammilind:我已经编辑了这个问题,希望现在更容易理解。
  • 收到了您的问题。我认为 Nawaz 的回答和他对另一个问题的链接很有用。

标签: c++ template-specialization iterator-traits


【解决方案1】:

您可以很好地将Container 用作iterator_traits 的模板参数。对 STL 的其余部分而言重要的是特征类中的 typedef,例如 value_type。这些应该设置正确:

template <class Container> struct iterator_traits
{
    public:
        typedef typename Container::value_type value_type;
    // etc.
};

然后,您将使用 value_type,而之前使用的是 T

至于使用 traits 类,你当然可以用你的外部容器的类型来参数化它:

iterator_traits<TheContainer> traits;

当然,这假设 TheContainer 符合通用 STL 容器的合同并且正确定义了 value_type

【讨论】:

  • 不,抱歉,我忘了说 Container not 有所需的嵌套 typedef;事实上,这就是我想要专门化 iterator_traits 的主要原因(否则标准实现就可以了)。我将编辑问题。另外,我认为专注于容器类型也无济于事,因为标准算法会尝试使用iterator_traits&lt;Iterator&gt;
  • Container&lt;T&gt;::iterator 怎么样?如果它是正确的 STL 迭代器(不是原始指针),您应该能够从那里提取 value_type
  • 好吧,实际上容器和迭代器都有这样一个嵌套的 typedef,但是使用的是非标准名称(不是 value_type),因此标准的 iterator_traits 不能与它们一起使用。我需要专门化才能将这个 typedef“翻译”成一个叫做 value_type 的类型。但是由于 stl 算法使用迭代器类型作为参数来实例化特征类,所以我不能专门研究容器类型。
【解决方案2】:

是的。编译器无法从Container&lt;T&gt;::iterator 推断出T,因为它是不可推断的上下文,换句话说,这意味着,给定Container&lt;T&gt;::iteratorT 的值不能唯一且可靠地推断出来(参见this for detail explanation)。

解决此问题的唯一方法是您必须完全为您打算在程序中使用的每个可能的iterator 值专门化iterator_traits。没有通用的解决方案,因为您不能编辑 Container&lt;T&gt; 类模板。

【讨论】:

    【解决方案3】:

    Nawaz's answer 可能是大多数情况下的正确解决方案。但是,如果您尝试对许多实例化的 SomeContainerFromAThirdPartyLib&lt;T&gt; 类和少数函数(或未知数量的实例化但固定数量的函数,如果您正在编写自己的库可能会发生这种情况)执行此操作,则有另一种方式。

    假设我们得到以下(不可更改的)代码:

    namespace ThirdPartyLib
    {
        template <typename T>
        class SomeContainerFromAThirdPartyLib
        {
            public:
                typedef T ValueType;    //  not value_type! 
                //  no difference_type
    
                class iterator
                {
                    public:
                        typedef T ValueType;    //  not value_type! 
                        //  no difference_type
    
                        // obviously this is not how these would actually be implemented
                        int operator != (const iterator& rhs) { return 0; }
                        iterator& operator ++ () { return *this; }
                        T operator * () { return T(); }
                };
    
                // obviously this is not how these would actually be implemented      
                iterator begin() { return iterator(); }
                iterator end() { return iterator(); }
        }; 
    }
    

    我们为iterator_traits 定义一个包含必要的typedefs 的适配器类模板,并对其进行专门化以避免指针问题:

    namespace MyLib
    {
        template <typename T>
        class iterator_adapter : public T
        {
            public:
                // replace the following with the appropriate types for the third party iterator
                typedef typename T::ValueType value_type;
                typedef std::ptrdiff_t difference_type;
                typedef typename T::ValueType* pointer;
                typedef typename T::ValueType& reference;
                typedef std::input_iterator_tag iterator_category;
    
                explicit iterator_adapter(T t) : T(t) {}
        };
    
        template <typename T>
        class iterator_adapter<T*>
        {
        };
    }
    

    然后,对于我们希望能够使用SomeContainerFromAThirdPartyLib::iterator 调用的每个函数,我们定义一个重载并使用 SFINAE:

    template <typename iter>
    typename MyLib::iterator_adapter<iter>::difference_type
    count(iter begin, iter end, const typename iter::ValueType& val)
    {
        cout << "[in adapter version of count]";
        return std::count(MyLib::iterator_adapter<iter>(begin), MyLib::iterator_adapter<iter>(end), val);
    }
    

    我们可以这样使用它:

    int main()
    {
        char a[] = "Hello, world";
    
        cout << "a=" << a << endl;
        cout << "count(a, a + sizeof(a), 'l')=" << count(a, a + sizeof(a), 'l') << endl; 
    
        ThirdPartyLib::SomeContainerFromAThirdPartyLib<int> container;
        cout << "count(container.begin(), container.end(), 0)=";
        cout << count(container.begin(), container.end(), 0) << std;
    
        return 0;
    }
    

    您可以在http://ideone.com/gJyGxU 找到一个带有所需includes 和usings 的可运行示例。输出:

    a=你好,世界 计数(a, a + sizeof(a), 'l')=3 count(container.begin(), container.end(), 0)=[在 count 的适配器版本中]0

    不幸的是,有一些警告:

    • 正如我所说,需要为您计划支持的每个功能定义重载(findsort 等)。这显然不适用于 algorithm 中尚未定义的函数。
    • 如果不进行优化,可能会出现小的运行时性能损失。
    • 存在潜在的范围问题。

    关于最后一个,问题是在哪个命名空间中放置重载(以及如何调用std 版本)。理想情况下,它将位于ThirdPartyLib 中,以便可以通过依赖于参数的查找找到它,但我假设我们无法更改它。下一个最佳选项是MyLib,但调用必须有资格或前面有using。在任何一种情况下,最终用户都应该使用using std::count;,或者注意哪些调用符合std::的条件,因为如果std::count被错误地与SomeContainerFromAThirdPartyLib::iterator一起使用,它显然会失败(这个练习的全部原因)。

    为了完整起见,我不建议但在此展示的另一种方法是将其直接放在 std 命名空间中。这会导致未定义的行为;虽然它可能对您有用,但标准中没有任何东西可以保证它。如果我们专门化 count 而不是重载它,这将是合法的。

    【讨论】:

      【解决方案4】:

      在所讨论的特化中,T 处于不可推导的上下文中,但不需要更改第三方库容器代码,也不需要在 std 命名空间中进行任何特化。

      如果第三方库没有在各自的命名空间中提供任何免费的 beginend 函数,则可以编写自己的函数(如果需要启用 ADL,则写入该命名空间)并将迭代器包装到自己的包装类中进而提供必要的 typedef 和操作符。

      第一个需要迭代器包装器。

      #include <cstddef>
      
      namespace ThirdPartyStdAdaptor
      {
      
        template<class Iterator>
        struct iterator_wrapper
        {
          Iterator m_it;
          iterator_wrapper(Iterator it = Iterator())
            : m_it(it) { }
          // Typedefs, Operators etc.
          // i.e.
          using value_type = typename Iterator::ValueType;
          using difference_type = std::ptrdiff_t;
          difference_type operator- (iterator_wrapper const &rhs) const
          {
            return m_it - rhs.m_it;
          }
        };
      
      }
      

      注意:也可以使 iterator_wrapper 继承自 Iterator,或者使其更通用,并使用另一个帮助器来启用其他迭代器的包装。 p>

      现在begin()end()

      namespace ThirdPartyLib
      {
        template<class T>
        ThirdPartyStdAdaptor::iterator_wrapper<typename 
          SomeContainer<T>::iterator> begin(SomeContainer<T> &c)
        {
          return ThirdPartyStdAdaptor::iterator_wrapper<typename
            SomeContainer<T>::iterator>(c.begin());
        }
        template<class T>
        ThirdPartyStdAdaptor::iterator_wrapper < typename
          SomeContainer<T>::iterator > end(SomeContainer<T> &c)
        {
          return ThirdPartyStdAdaptor::iterator_wrapper < typename
            SomeContainer<T>::iterator > (c.end());
        }
      }
      

      (也可以将它们放在与 SomeContainer 不同的命名空间中,但 ADL 松散。如果该容器的命名空间中存在 beginend 函数,我倾向于将适配器重命名为类似于wbeginwend。)

      现在可以使用这些函数调用标准算法:

      ThirdPartyLib::SomeContainer<SomeType> test;
      std::ptrdiff_t d = std::distance(begin(test), end(test));
      

      如果 begin()end() 包含在库命名空间中,则容器甚至可以在更通用的上下文中使用。

      template<class T>
      std::ptrdiff_t generic_range_size(T const &x)
      {
        using std::begin;
        using std::end;
        return std::distance(begin(x), end(x));
      }
      

      只要 ADL 找到返回包装迭代器的 begin()end(),此类代码就可以与 std::vectorThirdPartyLib::SomeContainer 一起使用。

      【讨论】:

        猜你喜欢
        • 2012-09-25
        • 2018-11-02
        • 2017-09-06
        • 2020-06-16
        • 2018-05-10
        • 2019-06-04
        • 1970-01-01
        • 2020-05-08
        • 1970-01-01
        相关资源
        最近更新 更多