简体   繁体   English

专门化iterator_traits

[英]specializing iterator_traits

I'd like to specialize std::iterator_traits<> for iterators of a container class template that does not have the usual nested typedefs (like value_type , difference_type , etc.) and whose source I shouldn't modify. 我想专门std::iterator_traits<>对于具有通常的嵌套的typedef(等的容器类模板的迭代器value_typedifference_type等)和其源我不应该修改。 Basically I'd like to do something like this: 基本上我想做这样的事情:

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

except that this doesn't work, as the compiler is unable to deduce T from Container<T>::iterator . 除了这不起作用,因为编译器无法从Container<T>::iterator推导出T

Is there any working way to achieve the same? 是否有任何工作方式来实现相同的目标?


For example: 例如:

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

Now suppose I call std::count() using an instance of this class. 现在假设我使用此类的实例调用std::count() As far as I know, in most STL implementations, count() returns iterator_traits<Iterator>::difference_type . 据我所知,在大多数STL实现中, count()返回iterator_traits<Iterator>::difference_type The primary template of iterator_traits<I> simply does typedef typename I::difference_type difference_type . iterator_traits<I>的主要模板只是typedef typename I::difference_type difference_type Same with the other nested types. 与其他嵌套类型相同。

Now in our example this obviously won't work, as there's no Container::iterator::difference_type . 现在在我们的例子中,这显然不起作用,因为没有Container::iterator::difference_type I thought I could work around this without modifying the iterator class, by specializing iterator_traits for iterators of any Container<T> . 我以为我可以解决这个不修改迭代器类,由专门iterator_traits任何的迭代器Container<T>

In the end, I just want to be able to use std algorithms like count, find, sort, etc., preferably without modifying any existing code. 最后,我只想使用std算法,如count,find,sort等,最好不要修改任何现有的代码。 I thought that the whole point of iterator_traits is exactly that: being able to specify types (like value_type , diff_type etc.) for iterator types that do not support them built-in. 我认为iterator_traits的重点在于:能够为不支持内置类型的迭代器类型指定类型(如value_typediff_type等)。 Unfortunately I can't figure out how to specialize the traits class for all instances of Container<T> . 不幸的是,我无法弄清楚如何为Container<T>所有实例专门化traits类。

Yes. 是。 The compiler cannot deduce T from Container<T>::iterator because it is non-deducible context, which in other words means, given Container<T>::iterator , the value of T cannot uniquely and reliably be deduced (see this for detail explanation ). 编译器不能从Container<T>::iterator推导出T ,因为它是不可导入的上下文,换句话说,在给定Container<T>::iteratorT的值不能唯一且可靠地推导出来(参见此内容)详细解释 )。

The only solution to this problem is that you've to fully specialize iterator_traits for each possible value of iterator which you intend to use in your program. 这个问题的唯一解决方案是你要为你想在程序中使用的iterator iterator_traits的每个可能值完全专门化iterator_traits There is no generic solution, as you're not allowed to edit the Container<T> class template. 没有通用的解决方案,因为您不允许编辑Container<T>类模板。

Nawaz's answer is likely the right solution for most cases. 对于大多数情况, Nawaz的答案可能是正确的解决方案。 However, if you're trying to do this for many instantiated SomeContainerFromAThirdPartyLib<T> classes and only a few functions (or an unknown number of instantiations but a fixed number of functions, as might happen if you're writing your own library), there's another way. 但是,如果您尝试为许多实例化的SomeContainerFromAThirdPartyLib<T>类以及仅少数函数(或未知数量的实例化但是固定数量的函数,如果您正在编写自己的库时可能会发生),还有另一种方式。

Assume we're given the following (unchangeable) code: 假设我们得到以下(不可更改的)代码:

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

We define an adapter class template containing the necessary typedef s for iterator_traits and specialize it to avoid problems with pointers: 我们定义了一个适配器类模板,其中包含iterator_traits所需的typedef ,并将其专门化以避免指针出现问题:

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

Then, for each function we want to be able to call with a SomeContainerFromAThirdPartyLib::iterator , we define an overload and use SFINAE: 然后,对于我们希望能够使用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);
}

We can then use it as follows: 然后我们可以按如下方式使用它:

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

You can find a runnable example with the required include s and using s at http://ideone.com/gJyGxU . 您可以找到所需的可运行的例子include S和using s的http://ideone.com/gJyGxU The output: 输出:

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

Unfortunately, there are caveats: 不幸的是,有一些警告:

  • As I said, an overload needs to be defined for each function you plan to support ( find , sort , et cetera). 正如我所说,需要为您计划支持的每个函数定义重载( findsort等)。 This obviously won't work for functions in algorithm that haven't been defined yet. 这显然不适用于尚未定义的algorithm中的函数。
  • If not optimized out, there may be small run-time performance penalties. 如果没有优化,可能会有小的运行时性能损失。
  • There are potential scoping issues. 存在潜在的范围问题。

In regards to that last one, the question is in which namespace to put the overload (and how to call the std version). 关于最后一个,问题是在哪个命名空间中放置重载(以及如何调用std版本)。 Ideally, it would be in ThirdPartyLib so that it could be found by argument-dependant lookup, but I've assumed we can't change that. 理想情况下,它将在ThirdPartyLib以便可以通过参数依赖查找找到它,但我认为我们无法改变它。 The next best option is in MyLib , but then the call has to be qualified or preceded by a using . 下一个最佳选择是在MyLib ,但是然后必须限定调用或在using之前。 In either case the end-user should either use using std::count; 在任何一种情况下,最终用户都应该using std::count; or be careful about which calls to qualify with std:: , since if std::count is mistakenly used with SomeContainerFromAThirdPartyLib::iterator , it will obviously fail (the whole reason for this exercise). 或者注意哪些调用符合std:: ,因为如果std::count错误地与SomeContainerFromAThirdPartyLib::iterator一起SomeContainerFromAThirdPartyLib::iterator ,它显然会失败(这个练习的全部原因)。

An alternative that I do not suggest but present here for completeness would be to put it directly in the std namespace. 不建议但在此处提供完整性的替代方法是将其直接放在std命名空间中。 This would cause undefined behavior; 这会导致未定义的行为; while it might work for you, there's nothing in the standard that guarantees it. 虽然它可能对你有用,但标准中没有任何东西可以保证它。 If we were specializing count instead of overloading it, this would be legal. 如果我们专门count而不是重载,这将是合法的。

In the specialization in question, T is in a nondeducible context but there is neither a third party library container code change nor any specialization in the std namespace required. 在所讨论的专业化中, T处于不可约的上下文中,但是既没有第三方库容器代码更改,也没有所需的std命名空间中的任何特化。

If the third party library does not provide any free begin and end functions in the respective namespace one can write own functions (into that namespace if desired to enable ADL) and wrap the iterator into an own wrapper class which in turn provides the necessary typedefs and operators. 如果第三方库没有在相应的命名空间中提供任何自由的beginend函数,则可以编写自己的函数(如果需要启用ADL,则进入该命名空间)并将迭代器包装到自己的包装类中,该包装类又提供必要的typedef和运营商。

First one needs the Iterator wrapper. 第一个需要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;
    }
  };

}

Note: It would also be possible to make iterator_wrapper inherit from Iterator , or to make it more generic and have another helper to enable the wrapping of other iterators as well. 注意:也可以使iterator_wrapper继承自Iterator ,或者使其更通用,并有另一个帮助程序来启用其他迭代器的包装。

Now begin() and end() : 现在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());
  }
}

(It is also possible to have them in a different namespace than SomeContainer but loose ADL. IF there are begin and end functions present in the namespace for that container I'd tend to rename the adaptors to be something like wbegin and wend .) (也可以将它们放在与SomeContainer不同的命名空间中,但松散的ADL。如果在该容器的命名空间中存在beginend函数,我倾向于将适配器重命名为wbeginwend 。)

The standard algorithms can be called using those functions now: 现在可以使用这些函数调用标准算法:

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

If begin() and end() are included into the library namespace, the container can even be used in more generic contexts. 如果将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));
}

Such code can be used with std::vector as well as ThirdPartyLib::SomeContainer , as long as ADL finds begin() and end() returning the wrapper iterator. 这样的代码可以与std::vector以及ThirdPartyLib::SomeContainer ,只要ADL找到begin()end()返回包装器迭代器。

You can very well use the Container as template parameter to your iterator_traits . 您可以很好地将Container作为模板参数用于iterator_traits What matters to the rest of STL are the typedefs inside your traits class, such as value_type . 对STL的其余部分重要的是traits类中的typedef,例如value_type Those should be set correctly: 那些应该正确设置:

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

You would then use value_type where you would previously use T . 然后,您将使用之前使用T value_type

As for using the traits class, you of course parametrize it with the type of your external container: 至于使用traits类,您当然可以使用外部容器的类型对其进行参数化:

iterator_traits<TheContainer> traits;

Naturally, this assumes TheContainer is conforms to the common STL containers' contract and has value_type defined correctly. 当然,这假设TheContainer符合常见的STL容器契约并且正确定义了value_type

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM