специализируется на 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. Basically I'd like to do something like this:

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

except that this doesn't work, as the compiler is unable to deduce T from Container::iterator.

Есть ли какой-либо рабочий способ добиться того же?


Например:

template 
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. As far as I know, in most STL implementations, count() returns iterator_traits::difference_type. The primary template of iterator_traits simply does 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. I thought I could work around this without modifying the iterator class, by specializing iterator_traits for iterators of any Container.

In the end, I just want to be able to use std algorithms like count, find, sort, etc., preferably without modifying any existing code. 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. Unfortunately I can't figure out how to specialize the traits class for all instances of Container.

7
Где объявляется Container ? Или это какой-нибудь Контейнер?
добавлено автор iammilind, источник
Я до сих пор не совсем понял вопрос. Можете ли вы обновить вопрос, например, в своем вопросе, как вы собираетесь его использовать? Я имею в виду, когда iterator_traits должен использовать класс по умолчанию и когда он должен использовать специализированную версию?
добавлено автор iammilind, источник
получил ваш вопрос. Я думаю, что ответ Наваза и его ссылка на другой вопрос полезны.
добавлено автор iammilind, источник
Любой контейнер, который сломал stl-support: он имеет итератор и const_iterator, они могут быть увеличены, уменьшены, разыменованы и т. Д., Но ни контейнер, ни итераторы не имеют вложенных в std-файлов вложенных typedefs.
добавлено автор imre, источник
@iammilind: Я отредактировал вопрос, надеюсь, теперь это станет понятным.
добавлено автор imre, источник

4 ответы

Yes. The compiler cannot deduce T from Container::iterator because it is non-deducible context, which in other words means, given Container::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 class template.

10
добавлено
+1 к вашему другому ответу также :)
добавлено автор iammilind, источник

Nawaz's answer is likely the right solution for most cases. However, if you're trying to do this for many instantiated SomeContainerFromAThirdPartyLib 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.

Предположим, нам предоставлен следующий (неизменный) код:

namespace ThirdPartyLib
{
    template 
    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 
    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 
    class iterator_adapter
    {
    };
}

Затем для каждой функции, которую мы хотим вызвать с помощью SomeContainerFromAThirdPartyLib :: iterator , мы определяем перегрузку и используем SFINAE:

template 
typename MyLib::iterator_adapter::difference_type
count(iter begin, iter end, const typename iter::ValueType& val)
{
    cout << "[in adapter version of count]";
    return std::count(MyLib::iterator_adapter(begin), MyLib::iterator_adapter(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 container;
    cout << "count(container.begin(), container.end(), 0)=";
    cout << count(container.begin(), container.end(), 0) << std;

    return 0;
}

Вы можете найти исполняемый пример с требуемым include s и с помощью s в 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 , но тогда вызов должен быть квалифицирован или должен предшествовать с помощью . В любом случае конечный пользователь должен либо использовать с помощью std :: count; , либо быть осторожным в отношении того, какие вызовы квалифицируются с помощью std :: , так как если std: : count ошибочно используется с SomeContainerFromAThirdPartyLib :: iterator , он, очевидно, потерпит неудачу (вся причина этого упражнения).

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

4
добавлено

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

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

Сначала нужна обертка Iterator.

#include 

namespace ThirdPartyStdAdaptor
{

  template
  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
  ThirdPartyStdAdaptor::iterator_wrapper::iterator> begin(SomeContainer &c)
  {
    return ThirdPartyStdAdaptor::iterator_wrapper::iterator>(c.begin());
  }
  template
  ThirdPartyStdAdaptor::iterator_wrapper < typename
    SomeContainer::iterator > end(SomeContainer &c)
  {
    return ThirdPartyStdAdaptor::iterator_wrapper < typename
      SomeContainer::iterator > (c.end());
  }
}

(Также возможно иметь их в другом пространстве имен, чем SomeContainer , но потерять ADL. ЕСЛИ существуют функции begin и end , присутствующие в пространстве имен для этого контейнера я бы переименовал адаптеры как нечто вроде wbegin и wend ).

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

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

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

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

1
добавлено

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

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

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

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

iterator_traits<theContainer> traits;

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

0
добавлено
Как насчет Container :: iterator ? Если это правильный STL-итератор (который не является необработанным указателем), вы можете извлечь из него value_type .
добавлено автор Xion, источник
Нет, извините, я забыл упомянуть, что у контейнера not есть необходимые вложенные typedefs; на самом деле это основная причина, по которой я хотел бы специализировать iterator_traits (иначе стандартная реализация была бы прекрасной). Я отредактирую вопрос. Кроме того, я думаю, что специализация по типу контейнера не помогла бы, так как std-алгоритмы попытались бы использовать iterator_traits .
добавлено автор imre, источник
Ну, на самом деле и контейнер, и итератор имеют такой вложенный typedef, но с нестандартным именем (not value_type), поэтому стандартный iterator_traits не будет работать с ними. Мне нужна специализация, чтобы «перевести» этот typedef в один, называемый value_type. Но так как stl-алгоритмы создают экземпляр класса признаков с использованием типа итератора в качестве параметра, я не могу специализироваться на типе контейнера.
добавлено автор imre, источник
pro.cxx
pro.cxx
3 049 участник(ов)

C/C++ chat 0. Простые вопросы, лабы и о IDE — в чат новичков @supapro 1. Не хамим, не переходим на личности, не вбрасываем утверждения без доказательств 2. No Ads, offtop, flood Объявления о вакансиях и евенты - в лс @AlexFails https://t.me/ProCxx/259155

supapro.cxx
supapro.cxx
1 925 участник(ов)

Чат для тех, кто немного знает C++, простые вопросы по реализации, синтаксису и ide – сюда, а для другого есть: /Главный чат по серьезным вопросам — @ProCxx /Чат по обсуждению всего — @fludpac

C++ Russia
C++ Russia
384 участник(ов)

Сообщество разработчиков C++ в Telegram.

cxx.Дискуссионная
cxx.Дискуссионная
298 участник(ов)

это не двач, общайтесь вежливо; разговор на почти любые темы; Не согласны с баном? В лс @AlexFails, @ivario

C++ для маленьких и тупых
C++ для маленьких и тупых
105 участник(ов)

Лоу левел (по среднему IQ участников) чатик ExtremeCode @extremecode Флудилка @extremecode_rest