iterator_traits özelleştirme

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.

Aynı şeyi elde etmek için herhangi bir çalışma yolu var mı?


Örneğin:

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 öğesi nerede bildirildi? Yoksa herhangi bir kapsayıcı mı?
katma yazar iammilind, kaynak
Hala soruyu tam olarak anlamadım. Sorunuzu, örneğin nasıl kullanacağınızı anlatan bir soruyla güncelleyebilir misiniz? Yani iterator_traits varsayılan sınıfı kullanmalı ve özel sürümü ne zaman kullanmalıdır?
katma yazar iammilind, kaynak
Sorunu aldım. Bence Nawaz'ın cevabı ve diğer soruyla olan bağlantısı yararlıdır.
katma yazar iammilind, kaynak
Stl desteğini kesen herhangi bir kapsayıcı: iterator ve const_iterator var, bunlar artırılabilir, azaltılabilir, dereferanslı olabilir, vs., ancak ne kapsayıcı ne de yineleyiciler, std uyumlu iç içe yazımlılara sahip değiller.
katma yazar imre, kaynak
@iammilind: Soruyu düzenledim, umarım artık daha anlaşılırdır.
katma yazar imre, kaynak

4 cevap

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
katma
Diğer cevabınıza +1 de :)
katma yazar iammilind, kaynak

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.

Aşağıdaki (değiştirilemez) kodu verdiğimizi varsayalım:

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

iterator_traits için gerekli typedef 'i içeren bir adaptör sınıfı şablonu tanımlıyoruz ve işaretçilerle ilgili sorunlardan kaçınmak için uzmanlaşıyoruz:

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

Ardından, her bir işlev için SomeContainerFromAThirdPartyLib :: yineleyici ile arama yapmak istediğimizde, bir aşırı yük tanımlar ve SFINAE kullanırız:

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

Bunu aşağıdaki gibi kullanabiliriz:

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

Çalışabilir bir örneği include ve kullanarak s kullanarak http://ideone.com/gJyGxU . Çıktı:

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

Maalesef, uyarılar var:

  • Dediğim gibi, desteklemeyi planladığınız her işlev için bir aşırı yüklenmenin tanımlanması gerekir ( find , sort , ve cetera). Bu, henüz tanımlanmayan algoritma işlevlerinde çalışmayacaktır.
  • Optimize edilmediyse, küçük çalışma süresi performans cezaları olabilir.
  • Potansiyel kapsam belirleme sorunları var.

Sonuncusuyla ilgili olarak, soru isim alanının aşırı yüklenmeyi (ve std versiyonunu nasıl çağırılacağı) ile ilgilidir. İdeal olarak, ThirdPartyLib 'da argüman bağımlı aramayla bulunabilir, ancak bunu değiştiremeyeceğimizi varsaydım. Sonraki en iyi seçenek MyLib 'dir, ancak aramanın kullanılarak tarafından nitelendirilmesi veya öncesinde yapılması gerekir. Her iki durumda da son kullanıcı, std :: count öğesini kullanarak kullanmalı veya std :: ile nitelendirilecek çağrılara dikkat etmelidir, çünkü std: : count yanlışlıkla SomeContainerFromAThirdPartyLib :: iterator ile kullanılır, açıkçası başarısız olacaktır (bu alıştırmanın tüm nedeni).

Önerme 'dir, ancak burada eksiksiz olması için bir alternatif, doğrudan std ad alanına koymak olacaktır. Bu tanımlanmamış davranışa neden olur; Sizin için işe yarayacak olsa da, standardı garanti eden hiçbir şey yoktur. Eğer aşırı yükleme yapmak yerine saymak konusunda uzmanlaşırsak bu yasal olur.

4
katma

Söz konusu uzmanlıkta, T , değiştirilemez bir bağlamdadır, ancak ne üçüncü parti bir kütüphane konteynır kodu değişikliği ne de std ad alanında gerekli herhangi bir uzmanlık yoktur.

Üçüncü taraf kütüphanesi, ilgili ad alanında boş begin ve end fonksiyonlarını sağlamazsa, kendi fonksiyonlarını (ADL'yi etkinleştirmek için istenirse bu isim alanına) yazabilir ve sırayla gerekli typedefs ve operatörleri sağlayan kendi sarmalayıcı sınıfına.

Birincisi yineleyici sarıcıya ihtiyaç duyar.

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

}

Not: Iterator_wrapper öğesinin Iterator öğesinden devralınması veya daha genel olması ve diğer yineleyicilerin de sarılmasını etkinleştirmek için başka bir yardımcıya sahip olması da mümkündür. .

Şimdi begin() ve 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());
  }
}

(Ayrıca SomeContainer 'dan farklı bir isim alanına sahip olmak, ancak ADL serbest bırakmak mümkündür. İsim alanında begin ve end işlevleri varsa Bu kapsayıcı için wbegin ve wend gibi bir şey olması için bağdaştırıcıları yeniden adlandırmaya eğilimliydim.)

Standart algoritmalar şu anda bu işlevleri kullanarak çağrılabilir:

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

Kütüphane ad alanına begin() ve end() dahil ise, kap daha genel bağlamlarda bile kullanılabilir.

template
std::ptrdiff_t generic_range_size(T const &x)
{
  using std::begin;
  using std::end;
  return std::distance(begin(x), end(x));
}

Bu kod, begin() ve std :: vector ile birlikte ThirdPartyLib :: SomeContainer ile kullanılabilir. > sarmalayıcı yineleyicisini iade eden son() .

1
katma

Kapsayıcı şablon parametresi olarak iterator_traits kodunuzu çok iyi kullanabilirsiniz. STL'nin geri kalanı için önemli olan, value_type gibi özellikler sınıfınızın içindeki yazım basamaklarıdır. Bunlar doğru şekilde ayarlanmalıdır:

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

Daha önce T 'i kullanacağınız value_type komutunu kullanacaksınız.

Özellik sınıfını kullanmak için, elbette, harici kabınızın türü ile parametrelendirebilirsiniz:

iterator_traits<theContainer> traits;

Doğal olarak, bu TheContainer , ortak STL kapsayıcısının sözleşmesiyle uyumlu olduğunu ve value_type öğesinin doğru tanımlandığını varsayar.

0
katma
Container :: yineleyici nedir? Eğer uygun bir STL yineleyici (ki bu bir ham işaretçi değil) ise, oradan value_type çıkarmalısınız.
katma yazar Xion, kaynak
Hayır, üzgünüm, Kapsayıcının gerekli iç içe yazım kurallarına sahip olmadığını belirtmeyi unuttum; Aslında iterator_traits (özellikle standart uygulama iyi olurdu) uzmanlaşmak istiyorum neden ana nedeni budur. Soruyu düzenleyeceğim. Ayrıca, std algoritmaları iterator_traits kullanmayı deneyeceğinden, kapsayıcı türünde uzmanlaşmanın yardımcı olamayacağını düşünüyorum.
katma yazar imre, kaynak
Aslında, hem kapsayıcı hem de yineleyici iç içe geçmiş bir yazım nesnesine sahip, ancak standart olmayan bir adla (value_type değil), dolayısıyla standart iterator_traits onlarla çalışmaz. Bu yazım değerini "value_type" olarak adlandırmak için bir uzmanlığa ihtiyacım var. Ancak, stl algoritmaları, özellik sınıfını parametre olarak yineleyici türünü kullanarak başlattığından, kapsayıcı türünde uzmanlaşamam.
katma yazar imre, kaynak