specializing iterator_traits

时光毁灭记忆、已成空白 提交于 2019-11-28 11:16:33
Nawaz

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).

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. There is no generic solution, as you're not allowed to edit the Container<T> class template.

jerry

Nawaz's answer is likely the right solution for most cases. 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.

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 typedefs for iterator_traits and specialize it to avoid problems with pointers:

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:

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 includes and usings at 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). This obviously won't work for functions in algorithm that haven't been defined yet.
  • 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). 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. The next best option is in MyLib, but then the call has to be qualified or preceded by a using. In either case the end-user should either use 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).

An alternative that I do not suggest but present here for completeness would be to put it directly in the std namespace. 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.

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.

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.

First one needs the Iterator wrapper.

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

Now begin() and 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.)

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.

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.

You can very well use the Container as template parameter to your iterator_traits. What matters to the rest of STL are the typedefs inside your traits class, such as 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.

As for using the traits class, you of course parametrize it with the type of your external container:

iterator_traits<TheContainer> traits;

Naturally, this assumes TheContainer is conforms to the common STL containers' contract and has value_type defined correctly.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!