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,
あなたの質問を得ました。私はNawazの答えと彼の他の質問へのリンクが役に立つと思います。
追加された 著者 iammilind,
stl-supportを壊しているコンテナ:iteratorとconst_iteratorを持ちますが、インクリメント、デクリメント、逆参照などが可能ですが、コンテナもイテレータもstd準拠のネストされたtypedefを持っていません。
追加された 著者 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(); }
    }; 
}

iterator_traits に必要な typedef を含むアダプタクラステンプレートを定義し、ポインタの問題を避けるためにそれを特化します:

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

includeusing を使用して、 > 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

残念ながら、警告があります:

  • 前述したように、サポートする予定の関数( findsort など)ごとにオーバーロードを定義する必要があります。これは明らかにまだ定義されていないアルゴリズムの関数では機能しません。
  • 最適化されていない場合は、実行時のパフォーマンスが低下する可能性があります。
  • スコープの問題が考えられます。

その最後のものに関しては、どの名前空間でオーバーロードするのか(そして std のバージョンを呼び出す方法)という質問があります。理想的には、 ThirdPartyLib にあるので、引数に依存する検索で見つけることができますが、私はそれを変更できないと想定しています。次の最適なオプションは MyLib ですが、呼び出しを修飾するか、 using する必要があります。どちらの場合でも、エンドユーザは std :: count; を使用するか、 std :: :count が誤って SomeContainerFromAThirdPartyLib :: iterator で使用されていると、明らかに失敗します(この演習の全理由)。

私が提案しないという代替案は、完全性のためにここに提示すると、 std 名前空間に直接配置することです。これは未定義の動作を引き起こします。それはあなたのために働くかもしれませんが、それを保証する何も標準にありません。オーバーロードするのではなく count に特化していれば、これは正当なものです。

4
追加された

特殊化の問題では、 T は無視できないコンテキストですが、必要な std ネームスペースにサードパーティのライブラリコンテナコードの変更や特殊化がありません。

サードパーティのライブラリがそれぞれのネームスペースにフリーの begin および end 関数を提供していない場合(ADLを有効にしたい場合は、そのネームスペースに)独自の関数を記述し、イテレータを独自のラッパークラスに変換します。これは、必要なtypedefと演算子を提供します。

最初に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_wrapperIterator から継承させたり、より一般的にしたり、他のイテレータのラッピングを有効にする別のヘルパーを持たせることもできます。

今すぐ 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が緩くなりますので、ネームスペースに beginend そのコンテナでは、アダプタの名前を wbeginwend のように変更する傾向があります。

これらの関数を使用して標準アルゴリズムを呼び出すことができます:

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

このようなコードは、ADLが begin()およびを見つけ出す限り、 std :: vector および ThirdPartyLib :: SomeContainer > end()でラッパーイテレータを返します。

1
追加された

iterator_traits には Container テンプレートパラメータを使用できます。残りのSTLにとって重要なのは、 value_type のような、あなたの特性クラス内のtypedefです。それらは正しく設定する必要があります:

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

以前に T を使用する場合は、 value_type を使用します。

traitsクラスを使用する場合は、もちろん外部のコンテナのタイプでパラメータ化する必要があります。

iterator_traits<theContainer> traits;

もちろん、これは TheContainer が共通のSTLコンテナの契約に準拠しており、 value_type が正しく定義されていることを前提としています。

0
追加された
Container :: iterator はどうでしょうか?それが正しいSTLイテレータ(生のポインタではない)なら、そこから value_type を抽出できるはずです。
追加された 著者 Xion,
いいえ、申し訳ありませんが、Containerは必須のネストされたtypedefを持たないことについて言及していませんでした。実際それがiterator_traitsを特化したい主な理由です(そうでなければ、標準実装は問題ありません)。私は質問を編集します。また、stdアルゴリズムは iterator_traits を使用しようとするので、コンテナの種類を特化することは役に立たないと思います。
追加された 著者 imre,
実際、コンテナとイテレータの両方にはネストされたtypedefがありますが、value_typeではなく非標準の名前が付いているため、標準のiterator_traitsは動作しません。このtypedefをvalue_typeと呼ばれるものに「翻訳」するためには、専門化が必要です。しかし、stlアルゴリズムはパラメータとしてイテレータ型を使用してtraitsクラスをインスタンス化するので、コンテナ型を特化することはできません。
追加された 著者 imre,