【C++】iterator_traitsに準拠する方法 - std::iteratorの問題と非推奨への対応

iterator_traits

std::iterator_traitsはイテレータの型情報やカテゴリを表現する型特性クラスです。

#include <iterator>
template<class Iterator> struct iterator_traits {
  using difference_type   = typename Iterator::difference_type;   // 減算演算時の型
  using value_type        = typename Iterator::value_type;        // 値型
  using pointer           = typename Iterator::pointer;           // ポインタ型
  using reference         = typename Iterator::reference;         // 参照型
  using iterator_category = typename Iterator::iterator_category; // カテゴリ型
};

iterator_traitsを用いることで、イテレータに準拠した対象クラスから、イテレータの種類や参照値の型などの情報を取得することができます。

using T = std::iterator<std::input_iterator_tag, int>;

std::iterator_traits<T>::value_type; // int
std::iterator_traits<T>::pointer;    // int*
std::iterator_traits<T>::reference   // int&
std::iterator_traits<T>::iterator_category; // input_iterator_tag

取得された情報は適切なアルゴリズムへの分岐や適用の際に活用することができます。また対象のイテレータがiterator_traitsに準拠していない場合や、求めるカテゴリでない場合には、エラーの発生につなげることも可能となります。

iterator_traitsは特殊化により、ポインタ型への適用にも対応しています。

typename iterator_traits<int*>::value_type  i = 9;   // OK
typename iterator_traits<char*>::value_type c = 'c'; // OK

ただし、配列型には対応していません。

int a[] = {1, 2, 3};
typename iterator_traits<decltype(a)>::value_type x; // No type named 'value_type' in 'std::__1::iterator_traits<int [3]>'

イテレータ型やポインタ型以外の、とりわけiterator_traitsに準拠していない型についても同様のエラーが発生します。

struct X {};
typename iterator_traits<X>::pointer p; // No type named 'pointer' in 'std::__1::iterator_traits<X>'

iterator_traitsへ準拠する方法

std::iteratorの継承

std::iterator_traitsの特性を満たすクラスを宣言する方法として最も簡単な手段は、標準のイテレータ・クラス(std::iterator)の継承ですが、本クラスはC++17以降の規格では非推奨の機能になる予定です。

#include <iterator>

struct Range : std::iterator<std::input_iterator_tag, int> {
  value_type start, end;
};

typename std::iterator_traits<Range>::value_type i; // OK: typeof(i) == int

メンバ型の宣言

iterator_traitsは以下のメンバ型の宣言を必要とします。これらを独自に宣言することでiterator_traitsの要求する仕様を満たすことができます。

名称役割
difference_typeイテレータの減算演算時の型
value_typeイテレータの指す値型
pointerイテレータのポインタ型
referenceイテレータの参照型
iterator_categoryイテレータのカテゴリ型

宣言例は以下の通りです。

// メンバ型の宣言例(`std::iterator<std::input_iterator_tag, int>`相当)
struct Range {
  using difference_type   = ptrdiff_t;
  using value_type        = int;
  using pointer           = int*;
  using reference         = int&;
  using iterator_category = std::input_iterator_tag;
};

iterator基本クラスの問題点

std::iteratorによる簡略化した宣言は便利な半面、分かりづらい挙動をとる場合があります。以下のようなテンプレート仮引数Tを指定するような例では、value_typeの名前解決が行えなるという問題を引き起こします。

template<typename T>
struct Iterator : std::iterator<std::input_iterator_tag, T> {
  value_type data; // ERROR: Unknown type name 'value_type'
  typename std::iterator<std::input_iterator_tag, T>::value_type data; // OK
};

次のように、自前で型を定義したほうが明確です。扱う側の混乱も少なくなります。

template<typename T>
struct Iterator {
  using value_type = T;
};

iteratorの基本型を独自定義する

一応、廃止予定のstd::iteratorをシュミレートする方法も紹介しておきます。メンバ型の面倒な独自宣言を簡略化する用途として利用できます。

template<class Category, class T, class Diff = ptrdiff_t, class Ptr = T*, class Ref = T&> struct basic_iterator {
  using difference_type  = Diff;
  using value_type       = T;
  using pointer          = Ptr;
  using reference        = Ref;
  using terator_category = Category;
};
struct Range : basic_iterator<std::input_iterator_tag, int> {
  value_type start, end;
};
広告
広告