专门化iterator_traits

时间:2011-10-28 09:58:17

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

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

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

除了这不起作用,因为编译器无法从T推断出Container<T>::iterator

有没有办法实现同样的目标?


例如:

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<Iterator>::difference_typeiterator_traits<I>的主要模板仅执行typedef typename I::difference_type difference_type。与其他嵌套类型相同。

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

最后,我只想使用std算法,如count,find,sort等,最好不要修改任何现有代码。我认为iterator_traits的重点正是:能够为不支持内置类型的迭代器类型指定类型(如value_typediff_type等)。不幸的是,我无法弄清楚如何为Container<T>的所有实例专门化traits类。

4 个答案:

答案 0 :(得分:10)

是。编译器无法从T推导出Container<T>::iterator因为它是不可导入的上下文,换句话说,在给定Container<T>::iterator的情况下,T的值不能唯一且可靠地推导出来(见this for detail explanation)。

此问题的唯一解决方案是,您要为您打算在程序中使用的iterator_traits的每个可能值完全专门化iterator。没有通用解决方案,因为您不允许编辑Container<T>类模板。

答案 1 :(得分:4)

对于大多数情况,

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

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

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

我们定义了一个适配器类模板,其中包含typedef所需的iterator_traits s并专门用于避免指针出现问题:

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找到包含所需includeusing s的可运行示例。输出:

a=Hello, world
count(a, a + sizeof(a), 'l')=3
count(container.begin(), container.end(), 0)=[in adapter version of count]0

不幸的是,有一些警告:

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

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

不建议的替代方案,但在此处提供完整性将是将其直接放在std命名空间中。这会导致未定义的行为;虽然它可能对你有用,但标准中没有任何东西可以保证它。如果我们专门化count而不是重载它,这将是合法的。

答案 2 :(得分:1)

在有问题的专业化中,T处于不可约的上下文中,但既没有第三方库容器代码更改,也没有std命名空间中的任何特化。

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

第一个需要Iterator包装器。

#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,或者使其更通用,并有另一个帮助程序来启用其他迭代器的包装。

现在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));
}

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

答案 3 :(得分:0)

您可以使用Container作为模板参数来iterator_traits。对STL的其余部分来说最重要的是traits类中的typedef,例如value_type。那些应该正确设置:

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

然后,您可以使用之前使用value_type的{​​{1}}。

至于使用traits类,您当然会使用外部容器的类型对其进行参数化:

T

当然,这假设iterator_traits<TheContainer> traits; 符合常见的STL容器合同,并且TheContainer已正确定义。