специализирующий iterator_traits - PullRequest
       46

специализирующий iterator_traits

8 голосов
/ 28 октября 2011

Я бы хотел специализироваться std::iterator_traits<> для итераторов шаблона класса контейнера, который не имеет обычные вложенные определения типа (например, value_type, difference_type и т. Д.) И чей источник I не должен изменять. В основном я хотел бы сделать что-то вроде этого:

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_type. Основной шаблон iterator_traits<I> просто делает typedef typename I::difference_type difference_type. То же самое с другими вложенными типами.

Теперь в нашем примере это явно не сработает, так как нет Container::iterator::difference_type. Я думал, что мог бы обойти это без изменения класса итератора, специализируя iterator_traits для итераторов любого Container<T>.

В конце я просто хочу иметь возможность использовать стандартные алгоритмы, такие как count, find, sort и т. Д., Предпочтительно без изменения какого-либо существующего кода. Я думал, что весь смысл iterator_traits заключается именно в том, что он может указывать типы (например, value_type, diff_type и т. Д.) Для типов итераторов, которые не поддерживают их встроенными. К сожалению, я не могу понять, как специализировать класс черт для всех случаев Container<T>.

Ответы [ 4 ]

10 голосов
/ 28 октября 2011

Да.Компилятор не может вывести T из Container<T>::iterator, потому что это не выводимый контекст, что другими словами означает, что, учитывая Container<T>::iterator, значение T не может быть однозначно и надежно выведено (см. , это подробнообъяснение ).

Единственное решение этой проблемы состоит в том, что вы должны полностью специализировать iterator_traits для каждого возможного значения iterator, которое вы намерены использовать в своемпрограмма.Универсального решения не существует, поскольку вам не разрешено редактировать шаблон класса Container<T>.

4 голосов
/ 09 июня 2013

Ответ Наваза , вероятно, является правильным решением для большинства случаев. Однако, если вы пытаетесь сделать это для многих экземпляров 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 s для iterator_traits, и специализируем его, чтобы избежать проблем с указателями:

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

Вы можете найти работающий пример с необходимыми include с и using с при http://ideone.com/gJyGxU. Выход:

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

К сожалению, есть предостережения:

  • Как я уже говорил, необходимо определить перегрузку для каждой функции, которую вы планируете поддерживать (find, sort и так далее). Это очевидно не будет работать для функций в algorithm, которые еще не были определены.
  • Если не оптимизирован, могут быть небольшие потери производительности во время выполнения.
  • Возможны проблемы с областями видимости.

Что касается последнего, вопрос в том, в какое пространство имен поместить перегрузку (и как вызвать версию std). В идеале это должно быть в ThirdPartyLib, чтобы его можно было найти с помощью аргументно-зависимого поиска, но я предположил, что мы не можем это изменить. Следующий лучший вариант - MyLib, но затем вызов должен быть квалифицирован или перед using. В любом случае конечный пользователь должен либо использовать using std::count;, либо быть осторожным в отношении того, какие вызовы подпадают под std::, поскольку, если std::count ошибочно используется с SomeContainerFromAThirdPartyLib::iterator, он, очевидно, потерпит неудачу (вся причина этого упражнения ).

Альтернативой, которую я не предлагаю , но для полной полноты изложения, было бы поместить ее непосредственно в пространство имен std. Это приведет к неопределенному поведению; хотя это может работать для вас, в стандарте нет ничего, что гарантировало бы это. Если бы мы специализировали count вместо его перегрузки, это было бы законно.

1 голос
/ 14 марта 2015

В рассматриваемой специализации T находится в не выводимом контексте, но не требуется ни изменение кода контейнера сторонней библиотеки, ни какая-либо специализация в пространстве имен std.

Если сторонняя библиотекане предоставляет никаких свободных функций begin и end в соответствующем пространстве имен, можно написать собственные функции (в это пространство имен, если необходимо включить ADL) и обернуть итератор в собственный класс-оболочку, который, в свою очередь, предоставляет необходимые определения типов и операторы.

Сначала необходим обертка 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 или сделать его более универсальным.и иметь другого помощника для включения обтекания других итераторов.

Now 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. ЕСЛИ в пространстве имен присутствуют функции begin и end для этого контейнера, который я бы хотел переименовать в tон может быть похож на wbegin и wend.)

Стандартные алгоритмы теперь можно вызывать с помощью этих функций:

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() возвращая итератор оболочки.

0 голосов
/ 28 октября 2011

Вы можете очень хорошо использовать Container в качестве параметра шаблона для вашего iterator_traits. Для остальной части STL важны typedefs внутри вашего класса черт, такие как value_type. Они должны быть установлены правильно:

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

Затем вы использовали бы value_type там, где ранее использовали бы T.

Что касается использования класса признаков, вы, конечно же, можете параметризовать его типом внешнего контейнера:

iterator_traits<TheContainer> traits;

Естественно, это предполагает, что TheContainer соответствует стандартному контракту контейнеров STL и правильно определено value_type.

...