LoginSignup
219
199

More than 3 years have passed since last update.

イテレータの解説をするなんて今更佳代

Last updated at Posted at 2016-12-26

初心者C++Advent Calendar 2016

この記事は初心者C++Advent Calendar 2016 15日目の記事です
<< 14日目 | ポインタと仲良くなる話 【初心者C++er Advent Calendar 2016 - 14日目】 - をるふちゃんのブログ || 16日目 | C++の乱数ライブラリが使いにくい話 >>

遅刻してすみません。
DxLib解剖学LoadSoundMemとLoadBGM - DxLibEx Research Notes
を書くための調査をしていたら、相当時間を取られました。

今回初心者AdCはなんか過疎ってますねぇ・・・。ほんわかした記事が多い中、ハローワールド徹底解説というヤバそうな記事があったりします、いつものことか。

去年ほど初心者詐欺な記事がないので、C++erを簡易的にレベル分けすることで初心者詐欺を減らそうという試みが功を奏したのかなぁ?とこじつけておきます

追記: C++20以降のイテレータについて

コンセプト導入やcontiguous_iteratorという概念(メモリー上での連続)の追加、比較演算子の自動導出などにより、イテレータの作り方は新時代を迎えました。またコンセプト絡みでこれまでstd::iterator_traitsを利用してきたコードは変更することが望ましい状態になっています。

新時代のイテレータの作り方、使い方についてはC++ Advent Calender 2020の記事である
[C++] C++20からのイテレータの素行調査方法 - 地面を見下ろす少年の足蹴にされる私
を参照してください。

はじめに

皆様、ナマステ

今回はイテレータについて書いていきます。え?イテレータの解説をするなんて今更佳代だって?それを言ったらネタがない

イテレータとは

イテレータとはなにもC++に固有な概念ではありません。

イテレータ - Wikipedia
https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%86%E3%83%AC%E3%83%BC%E3%82%BF
イテレータ(英語: Iterator)とは、プログラミング言語において配列やそれに類似するデータ構造の各要素に対する繰返し処理の抽象化である。実際のプログラミング言語では、オブジェクトまたは文法などとして現れる。反復するためのものの意味で反復子(はんぷくし)と訳される。繰返子(くりかえし)という一般的ではない訳語もある。

一般には配列のようなものを順番にたどるためのものです。

C++におけるイテレータとは

まあその気になればなんでも作れますが、一般的に見かけるイテレータたちを。

コンテナに対するイテレータ

#include <iostream>

std::array<int, 5> arr{{ 1, 3, 9, 4 }};
for(auto it = std::begin(arr); it != std::end(arr); ++it){
    std::cout << *it << std::endl;
}

std::arraystd::vectorstd::unordered_mapに代表されるコンテナクラスはメンバ関数begin()end()を持っています。この2つのメンバ関数はstd::begin()/std::end()を介して呼び出すこともできます。

イテレータをすすめるにはoperator++を呼ぶか、std::next()が利用できます。

C形式の配列に対するイテレータ

つまりポインタです。

#include <iostream>

int arr[] = { 1, 3, 9, 4 };
for(int* it = std::begin(arr); it != std::end(arr); ++it){
    std::cout << *it << std::endl;
}

配列の長さを求めて足し算して・・・というコードを自分で書くのはだるいので、std::begin()/std::end()がイテレータと同じく利用できます。

streamに対するイテレータ

これはあまり馴染みが無いかもしれません。サンプルコードはcpprefjpのものです

istream_iterator - cpprefjp C++日本語リファレンス

cpprefjpより
#include <iostream>
#include <iterator>
#include <sstream>
#include <algorithm> // for_each

int main()
{
  // 文字列の入力ストリームにデータを入れる
  std::stringstream ss;
  ss << 1 << std::endl
     << 2 << std::endl
     << 3;

  // 文字列の入力ストリームからデータを読み込むイテレータを作る
  std::istream_iterator<int> it(ss);
  std::istream_iterator<int> last;

  // イテレータを進めることにより、入力ストリームからデータを順に読み取る
  std::for_each(it, last, [](int x) {
    std::cout << x << std::endl;
  });
}

イテレータの種類

EZ-NET: 独自のイテレータを実装する - C++ プログラミング
http://program.station.ez-net.jp/special/handbook/cpp/stl/iterator-make.asp

にキレイにまとまっています。

cpprefjp
cpprefjp CC-BY

ついでに
iterator - cpprefjp C++日本語リファレンス
https://cpprefjp.github.io/reference/iterator.html

も見るといいです。

Range-based forとイテレータ

メンバ関数begin()/end()というはじめと終わりを指すイテレータがあるクラスを、範囲を持つクラスとかRange Conceptを満たしたクラスとか言いますが、こういったクラスやC形式の配列などはRange-based forに渡すことができます。

つまり

Range-based_for不使用
#include <iostream>
#include <array>
int main()
{
    std::array<int, 3> arr = { 3, 4, 2 };
    for(auto it = arr.begin(); it != end(); ++it) std::cout << *it << ", ";
    return 0;
}

これが

Range-based_for使用
#include <iostream>
#include <array>
int main()
{
    std::array<int, 3> arr = { 3, 4, 2 };
    for(auto&& e : arr) std::cout << e << ", ";
    return 0;
}

こう書けるということです。ちなみにauto&&はRange-based forを使うときのおまじないなので、おまじないの原理については

を見てください。

ちなみに細工なしで書けるトリッキーなRange-based forの使い方としては

文字列リテラルはC形式の配列型
#include <iostream>
int main()
{
    for(auto&& c : "arikitari") std::cout << c << std::endl;
}

initizer_listはRangeConceptを満たす
#include <iostream>
int main()
{
    for(auto&& n : { 13, 32 }) std::cout << n << std::endl;
}

なんかがあります。

イテレータをラップするイテレータを作る

例えばopenFrameworksみたいに

struct Vec2f{
    float x; float y;
};
struct Circle {
    Vec2f pos;
    float size;
};

こんなクラスがあるとします。さて、

std::vector<Circle> circles;

こんなふうにvectorに入れて管理していたとして、posを取ってくるのに

std::vector<Circle> circles;
for(auto& c : circles){
    auto& p = c.pos;
    std::cout << p.x << p.y << std::endl;
}

のようにするのはだるい、どのくらいだるいかというと(ry。
最初からCircle::sizeなんか無視してCircle::posだけほしい。
つまりそのようなイテレータを自作すればいいんですね。

std::iteratorは非推奨になったから継承してはいけない

ここで注意点があります

C++1z 古くなった機能を非推奨化 - Faith and Brave - C++で遊ぼう
C++1zから、標準ライブラリのいくつかの機能が非推奨となります。非推奨となった機能は将来のバージョンで削除される可能性がありますのでご注意ください。

std::iteratorクラス

自作イテレータを作るときに基本クラスとして使用するstd::iteratorクラスですが、これを使ってもイテレータを定義するのはあまり簡単になりませんでした

このクラスを使用することによって問題がより複雑になってしまうケースもありましたので、非推奨となります

Boost.Iteratorのようなイテレータを簡単に定義するための新たな仕組みは提供されませんので、標準ライブラリの範囲では、イテレータは最初から最後まで自分で定義することになります。

はい。イテレータを自作するときはstd::iteratorを継承する、という常識は過去のものになります
イテレータを作る!みたいな解説サイトはたくさんありますが、この点に十分注意してください。

作ってみた

イテレーターをラップしてイテレータを作る練習
https://gist.github.com/yumetodo/b0f82fc44e0e4d842c45f7596a6a0b49

ちょっと抜粋します

まずはイテレータ

template<typename Iterator, std::enable_if_t<std::is_same<Circle, typename std::iterator_traits<Iterator>::value_type>::value, std::nullptr_t> = nullptr>
class circle_pos_iterator
#if __cplusplus < 201500 //C++17ではstd::iteratorは消える
    : std::iterator<typename std::iterator_traits<Iterator>::iterator_category, Vec2f>
#endif
{
private:
    using ref_iterator_type = Iterator;
    ref_iterator_type it_;
#if __cplusplus < 201500
    using base_type = std::iterator<typename std::iterator_traits<Iterator>::iterator_category, Vec2f>;
#endif
public:
    circle_pos_iterator() = default;
    circle_pos_iterator(Iterator it) noexcept : it_(it) {}
    circle_pos_iterator(const circle_pos_iterator&) = default;
    circle_pos_iterator(circle_pos_iterator&&) = default;
    circle_pos_iterator& operator=(const circle_pos_iterator&) = default;
    circle_pos_iterator& operator=(circle_pos_iterator&&) = default;
    ref_iterator_type get_raw_iterator() const { return it_; }
#if __cplusplus < 201500  
    using iterator_category = typename base_type::iterator_category;
    using value_type = typename base_type::value_type;
    using difference_type = typename base_type::difference_type;
    using pointer = typename base_type::pointer;
    using reference = typename base_type::reference;
#else
    using iterator_category = typename std::iterator_traits<Iterator>::iterator_category;
    using value_type = Vec2f;
    using difference_type = std::ptrdiff_t;
    using pointer = Vec2f*;
    using reference = Vec2f&;
#endif
    Vec2f& operator*() noexcept { return it_->pos; }
    Vec2f operator*() const noexcept { return it_->pos; }

もととなるイテレータの形がtemplate引数のIteratorです。さっきも言ったようにstd::iteratorは非推奨になるので#ifdefで分けています。

もとのイテレータをクラス内部に持っておいてこいつを操作しつつ、結果を返します。

イテレータクラスとしてpublicに型定義しておくべきなのは、iterator_category, value_type, difference_type, pointer, referenceあたりでしょうか。この辺は定義しておくことをおすすめします。特にiterator_categoryはさっき言ったイテレータの種類に当たるもので、SFINAEなんかで利用されることが多いので忘れずに定義しましょう。

あとはひたすらoperator overloadを書いているだけですが、もととなるイテレータの種類によっては定義できないoperatorもあるので

    template<std::enable_if_t<std::is_base_of<std::bidirectional_iterator_tag, iterator_category>::value, std::nullptr_t> = nullptr>
    circle_pos_iterator& operator--() noexcept{
        --this->it_;
        return *this;
    }

こんな感じで弾いています。ちなみにstd::enable_if_tはC++14からなので、C++11で使うときは自分でalias templateを書きましょう。

次にRange

次に、これをRange-based forに渡すためにRangeを作ります。

template<typename It>
class circle_pos_iterator_range{
public:
    using iterator = circle_pos_iterator<It>;
private:
    iterator begin_; iterator end_;
public:
    circle_pos_iterator_range() = delete;
    circle_pos_iterator_range(It begin, It end) : begin_(begin), end_(end) {}
    circle_pos_iterator_range(const circle_pos_iterator_range&) = default;
    circle_pos_iterator_range(circle_pos_iterator_range&&) = default;
    circle_pos_iterator_range& operator=(const circle_pos_iterator_range&) = default;
    circle_pos_iterator_range& operator=(circle_pos_iterator_range&&) = default;
    iterator& begin() noexcept { return this->begin_; }
    const iterator& begin() const noexcept { return this->begin_; }
    iterator& end() noexcept { return this->end_; }
    const iterator& end() const noexcept { return this->end_; }
};

begin()/end()があるだけでとくに何もしていません。

Factory関数

最後にこのRangeクラスのFactory関数を作ります。いわゆるmake_xxx系関数ですね。

なんで必要かというと、C++14まではクラスのテンプレート実引数推定ができないので

int main(void){
    std::vector<Circle> v(10);
    Circle arr[10] = {};
    for(auto&& p : circle_pos_iterator_range<std::vector<Circle>::iterator>(v)){
        std::cout << p.x << p.y << std::endl;
    }
    for(auto&& p : circle_pos_iterator_range<Circle*>(arr)){
        std::cout << p.x << p.y << std::endl;
    }

    return 0;
}

のようにtemplate実引数を書くことになってしまうんですね、ダサい。

そこで

template<typename It, std::enable_if_t<std::is_same<Circle, typename std::iterator_traits<It>::value_type>::value, std::nullptr_t> = nullptr>
circle_pos_iterator_range<It> make_circle_pos_iterator_range(It begin, It end){
    return {begin, end};
}
template<typename Container>
circle_pos_iterator_range<typename Container::iterator> make_circle_pos_iterator_range(Container c)
{
    return make_circle_pos_iterator_range(c.begin(), c.end());
}
template<std::size_t N>
circle_pos_iterator_range<Circle*> make_circle_pos_iterator_range(Circle (&arr)[N]){
    return make_circle_pos_iterator_range(std::begin(arr), std::end(arr));
}

のようにラッパー関数を書きます。これで

int main(void){
    std::vector<Circle> v(10);
    Circle arr[10] = {};
    for(auto&& p : make_circle_pos_iterator_range(v)){
        std::cout << p.x << p.y << std::endl;
    }
    for(auto&& p : make_circle_pos_iterator_range(arr)){
        std::cout << p.x << p.y << std::endl;
    }

    return 0;
}

のように書けます。

ちなみにC++17では

本の虫: C++17のクラスのテンプレート実引数推定
https://cpplover.blogspot.jp/2016/10/blog-post_11.html

クラスのテンプレート実引数推定があるのでこれはいらなかったりします。

コルーチンもyield文もないけどpythonのジェネレータが欲しいのでイテレータで頑張る

ごめんなさい、pythonのジェネレータの前にpython書いた事自体があんま無いです。

それはそうとして、Range-based forで延々と乱数を取ってくるイテレータを考えてみます。

pythonで言えば

from random import randint
def random_generator_range(n, min_, max_):
    while True:
        yield randint(min_, max_)

def random_generator_range_with_count(n, min_, max_):
    i = 0
    while i < n:
        yield randint(min_, max_)
        i += 1

print("generate: random_generator_range")
for n in random_generator_range(10, 0, 10):
    print(n)
    if 0 == n:
        break
print("generate: random_generator_range_with_count")
for n in random_generator_range_with_count(10, 0, 10):
    print(n)

こんなかんじかな?

ジェネレータってイテレータのシンタックスシュガーだよね?(無知)

python詳しくないので(2回目)よく知りませんが、コード例見ている感じこれイテレータですよね?(無知)

ジェネレータもyieldもC++にはないですが、イテレータならあります。ならば作れそうです。

実装した

というわけで作りました。

イテレータクラス

template<typename T>
class random_generator_iterator
    : public random_generator_iterator_base
#if __cplusplus < 201500 //C++17ではstd::iteratorは消える
    , public std::iterator<std::input_iterator_tag, T>
#endif
{
    //中略
    using distribution = uniform_normal_distribution<value_type>;
private:
    struct impl {
        distribution dist;
        std::reference_wrapper<std::mt19937> engine;
        bool next_is_end;
        impl() = default;
        impl(const impl&) = delete;
        impl(impl&&) = default;
        impl& operator=(const impl&) = delete;
        impl& operator=(impl&&) = default;
        impl(value_type min, value_type max, std::mt19937& mt) : dist(min, max), engine(mt), next_is_end(false) {}
        value_type generate() { return this->dist(this->engine.get()); }
    };
    std::unique_ptr<impl> pimpl_;
    bool is_end_iterator_;
public:
    constexpr random_generator_iterator() noexcept : pimpl_(), is_end_iterator_(true) {};
    random_generator_iterator(const random_generator_iterator&) = delete;
    random_generator_iterator(random_generator_iterator&&) = default;
    random_generator_iterator& operator=(const random_generator_iterator&) = delete;
    random_generator_iterator& operator=(random_generator_iterator&&) = default;

    random_generator_iterator(value_type min, value_type max, std::mt19937& mt)
        : pimpl_(std::make_unique<impl>(min, max, mt)), is_end_iterator_(false)
    {}
    void stop() noexcept { this->pimpl_->next_is_end = true; }
    value_type operator*() { return this->pimpl_->generate(); }
    random_generator_iterator& operator++() noexcept
    { 
        if (this->pimpl_->next_is_end) this->is_end_iterator_ = true;
        return *this;
    }
    random_generator_iterator operator++(int) noexcept
    {
        const auto re = *this;
        if (this->pimpl_->next_is_end) this->is_end_iterator_ = true;
        return re;
    }
    constexpr bool operator==(const random_generator_iterator& r) const noexcept { return this->is_end_iterator_ == r.is_end_iterator_; }
    constexpr bool operator!=(const random_generator_iterator& r) const noexcept { return !(*this == r); }
};

まず、乱数生成器をどうするかという問題があります。

C++の乱数ライブラリは

  • 乱数生成器
  • Distribution(ディストリビューション)

を分離した設計になっています。ここで乱数生成器は基本的には各スレッドで一個持って使いまわすことを想定しています
つまり乱数生成を行うイテレータは乱数生成器への参照を保持する必要があります
ということはstd::reference_wrapperの出番ですね!

range-based forの注意点

C++14までは、range-based forは

C++14
{
    auto && __range = range_expression ; 
    for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) { 
        range_declaration = *__begin; 
        loop_statement 
    } 
} 

のシンタックスシュガーとして定義されていました。つまり、beginイテレータとendイテレータは同じ型である必要がありました。

C++17からは

C++17
{
    auto && __range = range_expression ; 
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) { 
        range_declaration = *__begin; 
        loop_statement 
    } 
} 

のシンタックスシュガーとなるので、違う型が利用できます。

Forward イテレータの注意点

Forward イテレータはDefaultConstructible である必要があります。

つまりDefaultConstructibleにしないといけない

C++14まででは、endイテレータの実装のために、DefaultConstructibleにしたほうが楽という事情と、Forward イテレータの要件でDefaultConstructibleを要求されているという理由から、デフォルトコンストラクタを作る必要があります。
しかし、std::reference_wrapperはDefaultConstructibleではありません。

そこで、pimplイデオムを使っています。

【c++】激震が走った、Pimplイディオム - Qiita
http://qiita.com/ashdik/items/b6b9924113f7e8d531cf

Rangeクラス

template<typename T>
class random_generator_range {
public:
    using value_type = T;
    using iterator = random_generator_iterator<value_type>;
private:
    value_type min_;
    value_type max_;
    std::reference_wrapper<std::mt19937> mt_;
public:
    random_generator_range() = delete;
    random_generator_range(const random_generator_range&) = delete;
    random_generator_range(random_generator_range&&) = default;
    random_generator_range& operator=(const random_generator_range&) = delete;
    random_generator_range& operator=(random_generator_range&&) = delete;
    random_generator_range(value_type min, value_type max, std::mt19937& mt)
        : min_(min), max_(max), mt_(mt) {}
    iterator begin() noexcept { return{ min_, max_, mt_.get() }; }
    iterator end() noexcept { return{}; }
};

まあとくに難しいことはしていないですね。

できたもの

static auto engine = create_engine();
int main()
{
    static_assert(is_random_generator_iterator_v<random_generator_iterator<int>>, "err");
    std::cout << "generate: random_generator_iterator" << std::endl;
    for (auto ri = random_generator_iterator<int>(0, 10, engine); ri != random_generator_iterator<int>{}; ++ri) {
        const auto n = *ri;
        std::cout << n << std::endl;
        if (0 == n) ri.stop();
    }
    std::cout << "generate: random_generator_range" << std::endl;
    for (auto&& n : random_generator_range<int>(0, 10, engine)) {
        std::cout << n << std::endl;
        if (0 == n) break;
    }
    static_assert(is_random_generator_iterator_v<random_generator_iterator_with_count<int>>, "err");
    std::cout << "generate: random_generator_iterator_with_count" << std::endl;
    for (auto ri = random_generator_iterator_with_count<int>(0, 10, engine); ri != random_generator_iterator_with_count<int>(10); ++ri) {
        std::cout << *ri << std::endl;
    }
    std::cout << "generate: random_generator_range_with_count" << std::endl;
    for (auto&& n : random_generator_range_with_count<int>(10, 0, 10, engine)) {
        std::cout << n << std::endl;
    }

    return 0;
}

同様にして、random_generator_iterator_with_countrandom_generator_range_with_countも作っています。

create_engineは乱数生成器を初期化して返しています。中では
https://github.com/yumetodo/random_generator_iterator/blob/master/random.hpp
相当いろいろやっています。正直ここまで凝ったseed生成は必要ない・・・。

int.TryParseが欲しい

int.TryParseというのはC#というか.NETにあるやつですね。

Int32.TryParse メソッド (String, Int32) (System) | MSDN

public static bool TryParse(
    string s,
    out int result
)
int value;
int.TryParse( "100" , out value );//valueは100

もちろんC++にもC++11でstd::stoiが追加されたので、

const int value = std::stoi("100");//100

のようにかけます。

しかし、例外を使うというのと、c形式の文字列でも一度std::stringを構築しないといけないという問題があります。

int.TryParseを自作しよう

つまり、std::stoiの実装にも使われるstrtol系関数を呼び出せばいいわけです。

strtol系関数を使うときの注意点

ところがこの関数、正しく使うのはなかなか大変だったりします。

C言語で安全に標準入力から数値を取得 - Qiita
http://qiita.com/yumetodo/items/238751b879c09b56234b

でも触れましたが、改めて書いておきましょう。

C11規格書によれば
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

7.22.1.4 The strtol, strtoll, strtoul, and strtoull functions
Synopsis

1

#include <stdlib.h>
long int strtol(
const char * restrict nptr,
char ** restrict endptr,
int base);
long long int strtoll(
const char * restrict nptr,
char ** restrict endptr,
int base);
unsigned long int strtoul(
const char * restrict nptr,
char ** restrict endptr,
int base);
unsigned long long int strtoull(
const char * restrict nptr,
char ** restrict endptr,
int base);

7 If the subject sequence is empty or does not have the expected form, no conversion is
performed; the value of nptr is stored in the object pointed to by endptr
, provided
that endptr is not a null pointer.
Returns

8 The strtol, strtoll, strtoul, and strtoull functions return the converted
value, if any. If no conversion could be performed, zero is returned. If the correct value
is outside the range of representable values, LONG_MIN, LONG_MAX, LLONG_MIN,
LLONG_MAX, ULONG_MAX, or ULLONG_MAX is returned (according to the return type
and sign of the value, if any), and the value of the macro ERANGE is stored in errno.

となっているので、エラーかどうか判定するには、戻り値やerrnoの値を確認するだけでは不充分endptrも渡して確認しないといけません。

蛇足ですが、Cでscanf系関数やatoi系関数を使ってはいけない理由はこれだったりします。

実装した

yumetodo / integer_parse_iterator / ソース / try_integer_parse.hpp — Bitbucket

template<typename T, concept_t<
    std::is_signed<T>::value && (sizeof(T) <= sizeof(long))
> = nullptr>
inline bool try_parse(const char* s, T& result, T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max()) noexcept
{
    errno = 0;
    char* endptr;
    const long t = strtol(s, &endptr, 10);
    if (0 != errno || (0 == t && endptr == s) || t < min || max < t) {
        return false;
    }
    else {
        result = static_cast<T>(t);
        return true;
    }
}

こんな感じ。実際にはすべての整数型に対応するために、もうすこしオーバーロードを書いているのでリンク先参照。

int.TryParseの問題点

TryParse系のメソッドで一時変数を用意したくない… - Qiita
http://qiita.com/Temarin/items/9aac6c1f569fc2113e0d

TryParse系のメソッドは、outやrefを使うので、一時変数を用意する必要がある。

C# 6.0でこんなのが予定されていたが、残念ながら却下されたようだ。

int.TryParse( "100" , out int value );

なんだその構文・・・、まあそれはさておき、パース結果が戻り値としてほしいですよね。constにできないし。

そこでイテレータですよ

これをみて思うんですね。

for (int n : integer_parse<int>(s)) {
    std::cout << n << std::endl;
}

こんな風にかければいいんじゃね?と。

sprout::optionalはそれ自身をコンテナにして、range-based forと組み合わせることで、無効なときは何もせず、有効なときは中身を取り出せるという素晴らしい機能を持っています。

同じように、パーサークラスを作って、range-based forと組み合わせることで、変換できないときは何もせず、変換したときは変換結果を取れればいいのではないでしょうか?

つまりそういうイテレータをかけばいいんですね

実装した

yumetodo / integer_parse_iterator / ソース / integer_parse_iterator.hpp — Bitbucket

まずはイテレータを

イテレータのコンストラクタで変換を先のtry_parseを呼び出して結果を保存しておくことにします。

template<typename T>
class integer_parse_iterator
{
    //中略
private:
    using lim = std::numeric_limits<value_type>;
    value_type re_;
    bool is_not_end_iterator_;
public:
    constexpr integer_parse_iterator() noexcept : re_(), is_not_end_iterator_(false) {}
    integer_parse_iterator& operator=(integer_parse_iterator&&) = default;
    integer_parse_iterator(const std::string& s, value_type min = lim::min(), value_type max = lim::min()) noexcept
    {
        this->is_not_end_iterator_ = try_parse(s, this->re_, min, max);
    }
    integer_parse_iterator(const char* s, value_type min = lim::min(), value_type max = lim::min()) noexcept
    {
        this->is_not_end_iterator_ = try_parse(s, this->re_, min, max);
    }
    value_type operator*() const noexcept { return this->re_; }
    integer_parse_iterator& operator++() noexcept
    {
        this->re_ = 0;
        this->is_not_end_iterator_ = false;
        return *this;
    }
    integer_parse_iterator operator++(int) noexcept
    {
        const auto re = *this;
        this->re_ = 0;
        this->is_not_end_iterator_ = false;
        return re;
    }
    //以下略
}

operator*は保存した結果を取るだけにして、インクリメントした時は問答無用でend iteratorにすることにします。

次にRange

変換を何度も行わないように、イテレータをメンバーに持つことにします。

template<typename T>
class integer_parse_range {
public:
    using value_type = T;
    using iterator = integer_parse_iterator<value_type>;
private:
    using lim = std::numeric_limits<value_type>;
    iterator it_;
public:
    integer_parse_range() = delete;
    integer_parse_range(const integer_parse_range&) = delete;
    integer_parse_range(integer_parse_range&&) = default;
    integer_parse_range& operator=(const integer_parse_range&) = delete;
    integer_parse_range& operator=(integer_parse_range&&) = delete;
    integer_parse_range(const std::string& s, value_type min = lim::min(), value_type max = lim::min()) noexcept
        :it_(s, min, max)
    {}
    integer_parse_range(const char* s, value_type min = lim::min(), value_type max = lim::min()) noexcept
        :it_(s, min, max)
    {}
    iterator begin() noexcept { return it_; }
    constexpr iterator end() const noexcept { return{}; }
};

最後にFactory関数を

template<typename T>
inline integer_parse_range<T> integer_parse(const std::string& s, T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max()) noexcept
{
    return{ s, min, max };
}
template<typename T>
inline integer_parse_range<T> integer_parse(const char* s, T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max()) noexcept
{
    return{ s, min, max };
}

できたもの

yumetodo / integer_parse_iterator / ソース / main.cpp — Bitbucket

int main()
{
    using std::string;
    std::cout << "try_parse" << std::endl;
    string s = "1123";
    {
        std::uint16_t re;
        if (try_parse(s, re)) {
            std::cout << re << std::endl;
        }
    }
    std::cout << "integer_parse_iterator(expect no output)" << std::endl;
    for (integer_parse_iterator<std::int8_t> it; it != integer_parse_iterator<std::int8_t>{}; ++it) {
        std::cout << *it << std::endl;
    }
    std::cout << "integer_parse" << std::endl;
    for (int n : integer_parse<int>(s)) {
        std::cout << n << std::endl;
    }
}

1123はstd::int8_tで表せない範囲なので1つ目のrange-based forの中身は実行されません。
1123は多くの処理系ではint型で表せるので、2つ目のrange-based forの中身は実行される処理系が多いと思います。

変換結果の変数はrange-based forの中でのみ有効になるので、スコープが大きくならずに済みます。

結論

イテレータはわりとなんでもできる。が、作るのに書くコードがものすごく多い。もう少しかんたんに書けるようになって欲しい。
逆に言えば行数でコードが評価されるような職場ではイテレータを増産しましょう

やっぱりC++17では入らなかったけど、非同期周り(co_async/co_await)とかコルーチンほしいよね。

Visual Studio 2015 Update 1 で C++ を試してみる - espresso3389の日記

#include <cstdio>
#include <random>
#include <experimental/generator>
auto random()
{
  std::mt19937 r;
  for (;;)
    yield r();
}
int main()
{
  for (auto r : random())
    std::printf("%d\n", r);
}

スッキリするし。


・・・あ、これ初心者C++Advent Calendarの記事なのに、SFINAEとか全く説明なしに書いてしまった。

License

CC BY 4.0

CC-BY icon.svg

初心者C++Advent Calendar 2016

明日はいなむ先生の記事です。

あらためて遅刻すみません。

<< 14日目 | ポインタと仲良くなる話 【初心者C++er Advent Calendar 2016 - 14日目】 - をるふちゃんのブログ || 16日目 | C++の乱数ライブラリが使いにくい話 >>

219
199
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
219
199