初心者C++Advent Calendar 2016
この記事は初心者C++Advent Calendar 2016 15日目の記事です
<< 14日目 | ポインタと仲良くなる話 【初心者C++er Advent Calendar 2016 - 14日目】 - をるふちゃんのブログ](http://wolf-cpp.hateblo.jp/entry/2016/12/20/063530) || [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::array
やstd::vector
、std::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++日本語リファレンス
#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
にキレイにまとまっています。
ついでに
iterator - cpprefjp C++日本語リファレンス
https://cpprefjp.github.io/reference/iterator.html
も見るといいです。
Range-based forとイテレータ
メンバ関数begin()
/end()
というはじめと終わりを指すイテレータがあるクラスを、範囲を持つクラスとかRange Conceptを満たしたクラスとか言いますが、こういったクラスやC形式の配列などは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;
}
これが
#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の使い方としては
#include <iostream>
int main()
{
for(auto&& c : "arikitari") std::cout << c << std::endl;
}
#include <iostream>
int main()
{
for(auto&& n : { 13, 32 }) std::cout << n << std::endl;
}
なんかがあります。
イテレータをラップするイテレータを作る
struct Circle {
— Yanai@ふちゃきちLove (@Lost206) 2016年11月4日
Vec2f pos;
float size;
}
vector<Circle> circles;
このcirclesからVec2f* (posのみの配列のポインタがほちい
例えば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は
{
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
のシンタックスシュガーとして定義されていました。つまり、beginイテレータとendイテレータは同じ型である必要がありました。
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_count
とrandom_generator_range_with_count
も作っています。
create_engine
は乱数生成器を初期化して返しています。中では
https://github.com/yumetodo/random_generator_iterator/blob/master/random.hpp
相当いろいろやっています。正直ここまで凝ったseed生成は必要ない・・・。
int.TryParse
が欲しい
C++ でint.tryParseみたいなのってあるのかな(´・ω・`)いまいちよく分からない・・
— イロハ㌠ (@iroha_thesleepy) 2016年12月24日
int.TryParse
というのはC#というか.NETにあるやつですね。
public static bool TryParse(
string s,
out int result
)
```csharp
int value;
int.TryParse( "100" , out value );//valueは100
もちろんC++にもC++11でstd::stoi
が追加されたので、
const int value = std::stoi("100");//100
のようにかけます。
@azaika_ なるほど。例外ですか。。。できればfalse返してほしかったw
— イロハ㌠ (@iroha_thesleepy) 2016年12月24日
しかし、例外を使うというのと、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
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](https://bitbucket.org/yumetodo/integer_parse_iterator/src/f99b31e98997fe524e1a0b884c94cded37303218/try_integer_parse.hpp?at=master&fileviewer=file-view-default)
```cpp
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にできないし。
## そこでイテレータですよ
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">C++のoptionalとかC#のNullableとか、最近になって0または1要素のコンテナに見えてきた。foreachに突っ込めてもいいと思うんだけど、現実はなぜか突っ込めない。不思議だ。</p>— プププランドのプリンス RiSK (@sscrisk) <a href="https://twitter.com/sscrisk/status/702699311050588160">2016年2月25日</a></blockquote>
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">sprout::optional にコンテナインタフェースを追加しました。<a href="https://t.co/CgbXSNpl5D">https://t.co/CgbXSNpl5D</a><a href="https://t.co/DFw7i4Nby2">https://t.co/DFw7i4Nby2</a></p>— 狂える中3女子ボレロ村上/陶芸C++er (@bolero_MURAKAMI) <a href="https://twitter.com/bolero_MURAKAMI/status/702792883791749120">2016年2月25日</a></blockquote>
これをみて思うんですね。
```cpp
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
)とかコルーチンほしいよね。
#include
#include
#include
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](https://creativecommons.org/licenses/by/4.0/deed.ja)
<a href="https://commons.wikimedia.org/wiki/File:CC-BY_icon.svg#/media/File:CC-BY_icon.svg"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/CC-BY_icon.svg/1200px-CC-BY_icon.svg.png" alt="CC-BY icon.svg" width="88px"></a>
# 初心者C++Advent Calendar 2016
明日はいなむ先生の記事です。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/yumetodo">@yumetodo</a> <a href="https://twitter.com/Lost206">@Lost206</a> はよ!</p>— いなむのみたま|超ウィザード級C++er (@enum_hack) <a href="https://twitter.com/enum_hack/status/809394764214743040">2016年12月15日</a></blockquote>
あらためて遅刻すみません。
[<< 14日目 | ポインタと仲良くなる話 【初心者C++er Advent Calendar 2016 - 14日目】 - をるふちゃんのブログ](http://wolf-cpp.hateblo.jp/entry/2016/12/20/063530) || [16日目 | C++の乱数ライブラリが使いにくい話 >>](http://qiita.com/_EnumHack/items/25fc998873d1bc7d2276)