SlideShare una empresa de Scribd logo
1 de 50
Descargar para leer sin conexión
Iterators Must Go

                            Andrei Alexandrescu




©2009 Andrei Alexandrescu
This Talk
 •   The STL
 •   Iterators
 •   Range-based design
 •   Conclusions




©2009 Andrei Alexandrescu
STLとは何か?




©2009 Andrei Alexandrescu
STLとは何か?
 • アルゴリズムとデータ構造の(良い|悪い|醜い)ライブラリ

 • iterators = gcd(containers, algorithms);

 • Scrumptious Template Lore(すばらしいテンプレートの知恵)

 • Snout to Tail Length(先頭から最後尾までの長さ)




©2009 Andrei Alexandrescu
STLとは何かというと
 • STLでは答えよりも質問の方が重要

 • “基本的なコンテナとアルゴリズムの中で最も一般的な
   実装はどのようなものでしょうか?”

 • ほかのものは全てその余波

 • STLで最も重要なこと:
       STLはひとつの答えではあるが、答えではない



©2009 Andrei Alexandrescu
STLは非直観的
 • 相対性理論が非直観的なのと同じ方向

 • 複素数が非直観的なのと同じ方向

                            n√-1形式の値は”虚数”だが
                            方程式で使用することができる。




©2009 Andrei Alexandrescu
非直観的
 • “私は最も一般的なアルゴリズムを設計したい。”

 • “もちろんできる。あなたが確実に必要とするものは、イテレータと呼ばれ
   るものである。正確に言うとそれらのうちの5つ。”

 • 証拠:どんな言語も”たまたま”STLをサポートしなかった。
      •   無慈悲な”機能戦争”にもかかわらず
      •   C++とDが唯一
      •   どちらもSTLのサポートを積極的に目指した


 • 結果:STLは、C++とDの外から理解するのが非常に困難になった。




©2009 Andrei Alexandrescu
Fundamental vs Incidental in STL
 (基礎 vs 偶発的なSTL)
 •    F:アルゴリズムは可能な限り狭いインタフェースで定義される

 •    F:アルゴリズムに必要とされる広いイテレータカテゴリ

 •    I:iterator primitiveの選択

 •    I:iterator primitiveの構文




©2009 Andrei Alexandrescu
STL:良い点
 • 正しい質問ができる

 • 一般的

 • 効率的

 • 無理なく拡張可能

 • 組み込み型に統合された


©2009 Andrei Alexandrescu
STL:悪い点
 • ラムダ関数のサポートが貧弱
      • STLの問題ではない
      • 高い機会費用


 • いくつかのコンテナはサポートできない
      – たとえば、sentinel-terminatedコンテナ
      – たとえば、分散ストレージを持つコンテナ


 • いくつかの反復法はサポートできない



©2009 Andrei Alexandrescu
STL:醜い点
 • for_each等の試みは助けにならなかった

 • ストリームによる統合が希薄

 • 1単語:allocator

 • イテレータの最低なところ
      – 冗長
      – 安全ではない
      – 貧弱なインタフェース


©2009 Andrei Alexandrescu
イテレータの取り決めは何か?




©2009 Andrei Alexandrescu
Iterators Rock
 • コンテナとアルゴリズム間の相互作用をまとめ上げる

 • “強度低下:”m・nの代わりにm+nを実装

 • 拡張可能:ここで、STLが日の目を見て以来、イテレータに動
   揺が走った




©2009 Andrei Alexandrescu
危険信号 #1
 • 2001年頃にC++ Users Journalは広告キャンペーンを行った
      – 「CUJに記事を投稿してください!」
      – 「英文学を専攻している必要はありません!
        エディタで始めてください!」
      – 「私たちはセキュリティ、ネットワーク技術、C++技術、その他の技術に
        興味があります!」

      注:まだ他のイテレータには興味がありませんでした


 • 公開されたイテレータはどれくらい生き残った?



©2009 Andrei Alexandrescu
危険信号 #2
 • およそ1975年のファイルコピー

 #include <stdio.h>
 int main() {
     int c;
     while ((c = getchar()) != EOF)
         putchar(c);
     return errno != 0;
 }




©2009 Andrei Alexandrescu
危険信号 #2
 • 20年ほど早送りして…

 #include <iostream>
 #include <algorithm>
 #include <iterator>
 #include <string>
 using namespace std;

 int main() {
     copy(istream_iterator<string>(cin),
        istream_iterator<string>(),
        ostream_iterator<string>(cout,"¥n"));
 }

©2009 Andrei Alexandrescu
(mainの中にtry/catchを書くのを忘れた)




©2009 Andrei Alexandrescu
(何かどこか、逆にひどくなってしまった)




©2009 Andrei Alexandrescu
危険信号 #3
 • イテレータは定義するのが非常に難しい

 • かさばった実装と多くの了解事項(gotcha)

 • Boostはイテレータの定義を支援する完全なライブラリを含ん
   でいる

 • 本質的なプリミティブは…3つ?
      – 終了(At end)
      – アクセス(Access)
      – 衝突(Bump)


©2009 Andrei Alexandrescu
危険信号 #4
 • イテレータはポインタの構文とセマンティクスを使用する

 • 勝利/敗北のポインタとの統合

 • しかし、これはイテレーションの方法を制限する
      – パラメータを持った++が必要なので、深いツリーを歩かせることがで
        きない

      – OutputIteratorはひとつの型しか受け取ることができない:
        それらは全て同じ場所へ行くが、output_iteratorは出力するそれぞれ
        の型でパラメータ化されなければならない


©2009 Andrei Alexandrescu
最終的な命取り
 • 全てのイテレータプリミティブは基本的に安全ではない

 • ほとんどのイテレータは、与えられたイテレータのために
      – 比較できるかどうかを記述することができない
      – インクリメントできるかどうかを記述することができない
      – 間接参照できるかどうかを記述することができない


 • 安全なイテレータを書くことができる
      – 高いサイズ+速度のコストで
      – たいていは、設計をカットできなかったというよい議論
        (訳注:かなり怪しい訳)


©2009 Andrei Alexandrescu
Range




©2009 Andrei Alexandrescu
Enter Range
 • これらの不便さを部分的に回避するため、
   Rangeが定義された

 • Rangeは、begin/endイテレータの組をパックしたもの

 • そのため、Rangeはより高レベルのcheckable invariant(訳注:
   チェック可能な不変式?)を持つ

 • BoostとAdobeのライブラリはRangeを定義した



©2009 Andrei Alexandrescu
それらはよい方向におもしろい一歩を踏み出した




©2009 Andrei Alexandrescu
より一層理解されなけばならない




©2009 Andrei Alexandrescu
ほら、どこにもイテレータがない!
 • イテレータの代わりにRangeをイテレーション用の
   基本構造として定義してはどうだろう?

 • Rangeは、イテレーションに依存しない基本処理を定義するべき

 • 今後イテレータはなく、Rangeだけが残るだろう(訳注:怪しい)

 • Rangeはどのプリミティブをサポートしなければならないか
      begin/endがオプションではないことを思い出してほしい
      人々が個々のイテレータを貯め込んでいたら、我々は振り出しに戻らなければならない




©2009 Andrei Alexandrescu
Rangeの定義
 • <algorithm>は全てRangeで定義でき、他のアルゴリズムも同様にRangeで
   実装できる

 • Rangeプリミティブは、少ないコストでチェック可能でなければならない

 • イテレータより非効率であってはならない




©2009 Andrei Alexandrescu
Input/Forward Range


       template<class T> struct InputRange {
           bool empty() const;
           void popFront();
           T& front() const;
       };




©2009 Andrei Alexandrescu
証明可能?
 template<class T> struct ContigRange {
     bool empty() const { return b >= e; }
     void popFront() {
         assert(!empty());
         ++b;
     }
     T& front() const {
         assert(!empty());
         return *b;
     }
 private:
     T *b, *e;
 };

©2009 Andrei Alexandrescu
検索

 // オリジナル版のSTL
 template<class It, class T>
 It find(It b, It e, T value) {
     for (; b != e; ++b)
     if (value == *b) break;
     return b;
 }
 ...
 auto i = find(v.begin(), v.end(), value);
 if (i != v.end()) ...




©2009 Andrei Alexandrescu
設計質問

 • Rangeでのfindはどのように見えなければならないか?
      1. 範囲のひとつの要素(見つかった場合)、
         もしくは0個の要素(見つからなかった場合)を返す?

      2. 見つかった要素以前のRangeを返す?

      3. 見つかった要素以降のRangeを返す?

 •   正解:(もしあった場合)見つかった要素で始まるRangeを返し、そうでなければ空
     を返す

     なぜ?




©2009 Andrei Alexandrescu
検索

 // Rangeを使用
 template<class R, class T>
 R find(R r, T value) {
     for (; !r.empty(); r.popFront())
         if (value == r.front()) break;
     return r;
 }
 ...
 auto r = find(v.all(), value);
 if (!r.empty()) ...




©2009 Andrei Alexandrescu
エレガントな仕様


     template<class R, class T>
     R find(R r, T value);



      「frontがvalueと等しいか、rが使い尽くされるまで、
        左から範囲rを縮小する」




©2009 Andrei Alexandrescu
Bidirectional Range


       template<class T> struct BidirRange {
           bool empty() const;
           void popFront();
           void popBack();
           T& front() const;
           T& back() const;
       };




©2009 Andrei Alexandrescu
Reverse Iteration
 template<class R> struct Retro {
     bool empty() const { return r.empty(); }
     void popFront() { return r.popBack(); }
     void popBack() { return r.popFront(); }
     E<R>::Type& front() const { return r.back(); }
     E<R>::Type& back() const { return r.front(); }
     R r;
 };
 template<class R> Retro<R> retro(R r) {
     return Retro<R>(r);
 }
 template<class R> R retro(Retro<R> r) {
     return r.r; // klever(訳注:clever? : 賢いところ)
 }
©2009 Andrei Alexandrescu
find_endはどうだろう?
     template<class R, class T>
     R find_end(R r, T value) {
         return retro(find(retro(r));
     }

 • rbegin, rendは必要ない
 • コンテナは、範囲を返すallを定義する
 • 後ろにイテレートする:retro(cont.all())




©2009 Andrei Alexandrescu
イテレータでのfind_endは最低だ
     // reverse_iteratorを使用したfind_end
     template<class It, class T>
     It find_end(It b, It e, T value) {
         It r = find(reverse_iterator<It>(e),
             reverse_iterator<It>(b), value).base();
         return r == b ? e : --r;
     }

 • Rangeが圧倒的に有利:はるかに簡潔なコード
 • 2つを同時に扱うのではなく、1つのオブジェクトのみから構
   成されるので、容易な構成になる



©2009 Andrei Alexandrescu
さらなる構成の可能性
 • Chain : いくつかのRangeをつなげる
         要素はコピーされない!
         Rangeのカテゴリは、全てのRangeの中で最も弱い


 • Zip : 密集行進法(lockstep)でRangeを渡る
           Tupleが必要


 • Stride : 一度に数ステップ、Rangeを渡る
           イテレータではこれを実装することができない!

 • Radial : 中間(あるいはその他のポイント)からの距離を増加させる際に
        Rangeを渡る


©2009 Andrei Alexandrescu
3つのイテレータの関数はどうだろうか?

 template<class It1, class It2>
 void copy(It1 begin, It1 end, It2 to);
 template<class It>
 void partial_sort(It begin, It mid, It end);
 template<class It>
 void rotate(It begin, It mid, It end);
 template<class It, class Pr>
 It partition(It begin, It end, Pr pred);
 template<class It, class Pr>
 It inplace_merge(It begin, It mid, It end);




©2009 Andrei Alexandrescu
「困難なところにはチャンスがある。」
         "Where there's hardship, there's opportunity."




                                                          – I. Meade Etop




©2009 Andrei Alexandrescu
3-legged algos ⇒ mixed-range algos
     template<class R1, class R2>
     R2 copy(R1 r1, R2 r2);

 • 仕様:r1からr2へコピーして、手をつけていないr2を返す

     vector<float> v;
     list<int> s;
     deque<double> d;
     copy(chain(v, s), d);




©2009 Andrei Alexandrescu
3-legged algos ⇒ mixed-range algos
     template<class R1, class R2>
     void partial_sort(R1 r1, R2 r2);

 • 仕様:最も小さな要素がr1の中で終了するように、r1とr2の連結を
   部分的にソートする

 • あなたは、vectorとdequeをとり、両方の中で最も小さな要素を配列に入
   れることができる

     vector<float> v;
     deque<double> d;
     partial_sort(v, d);



©2009 Andrei Alexandrescu
ちょっと待って、まだある
     vector<double> v1, v2;
     deque<double> d;
     partial_sort(v1, chain(v2, d));
     sort(chain(v1, v2, d));


 • アルゴリズムは今、余分な労力なしで任意のRangeの組み合わせにおい
   ても途切れることなく動作することができる

 • イテレータでこれを試してみて!




©2009 Andrei Alexandrescu
ちょっと待って、さらにある
     vector<double> vd;
     vector<string> vs;
     // 密集行進法(lockstep)で2つをソートする
     sort(zip(vs, vd));


 • Rangeコンビネータは、無数の新しい使い方ができるようになる

 • イテレータでも理論上は可能だが、(再び)構文が爆発する




©2009 Andrei Alexandrescu
Output Range
  •   ポインタ構文からの解放されたので、異なる型をサポートすることが可能になった




      struct OutRange {
          typedef Typelist<int, double, string> Types;
          void put(int);
          void put(double);
          void put(string);
      };




©2009 Andrei Alexandrescu
stdinからstdoutにコピーする話に戻ろう

       #include <...>
       int main() {
           copy(istream_range<string>(cin),
               ostream_range(cout, "¥n"));
       }

                                              1
 • 最後にもう一歩:1行(one-line)に収まる寸言 (one-liner)

 • ostream_rangeにはstringを指定する必要はない



  1
      スライド制限にもかかわらず



©2009 Andrei Alexandrescu
Infinite Range(無限の範囲)
 • 無限についての概念はRangeでおもしろくなる

 • ジェネレータ、乱数、シリーズ、… はInfinite Range

 • 無限は、5つの古典的カテゴリとは異なる特性;
   あらゆる種類のRangeが無限かもしれない

 • ランダムアクセスなRangeさえ無限かもしれない!

 • 無限について静的に知っておくことは、アルゴリズムを助ける




©2009 Andrei Alexandrescu
has_size
 • Rangeが効率的に計算されたsizeを持っているかどうかは他の独立した特
   性

 • 索引項目:list.size, 永遠の討論

 • Input Rangeさえ、既知のサイズを持つことができる
   (たとえば、100個の乱数をとるtake(100, rndgen))
      – rが無限の場合、take(100, r)は100の長さ
      – rが長さを知っている場合、長さはmin(100, r.size())
      – rが未知の長さで有限の場合、未知の長さ




©2009 Andrei Alexandrescu
予想外の展開(A Twist)
 • Rangeで<algorithm>をやり直すことができる?

 • Dのstdlibは、std.algorithmとstd.rangeモジュールで<algorithm>のスー
   パーセットを提供している(googleで検索してほしい)

 • RangeはD全体に渡って利用されている:
       アルゴリズム、遅延評価、乱数、高階関数、foreach文…

 • いくつかの可能性はまだ簡単には挙げられなかった
   – たとえばfilter(input/output range)

 • Doctor Dobb‘s Journalの「The Case for D」をチェックしておいてください。
   coming soon...



©2009 Andrei Alexandrescu
結論
 • Rangeは優れた抽象的概念である

 • より良いチェック能力(まだ完全ではない)

 • 容易な構成

 • Rangeに基づいた設計は、イテレータに基づいた関数をはるかに超えるも
   のを提供する

 • 一歩進んだSTLによる刺激的な開発




©2009 Andrei Alexandrescu

Más contenido relacionado

Similar a Iterators must-go(ja)

2016年第二回プレ卒研in山口研
2016年第二回プレ卒研in山口研2016年第二回プレ卒研in山口研
2016年第二回プレ卒研in山口研dmcc2015
 
Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~CHY72
 
DTrace for biginners part(2)
DTrace for biginners part(2)DTrace for biginners part(2)
DTrace for biginners part(2)Shoji Haraguchi
 
Enumはデキる子 ~ case .Success(let value): ~
 Enumはデキる子 ~ case .Success(let value): ~ Enumはデキる子 ~ case .Success(let value): ~
Enumはデキる子 ~ case .Success(let value): ~Takaaki Tanaka
 
中3女子でもわかる constexpr
中3女子でもわかる constexpr中3女子でもわかる constexpr
中3女子でもわかる constexprGenya Murakami
 
続・ゲンバのSwift
続・ゲンバのSwift続・ゲンバのSwift
続・ゲンバのSwiftYuichi Adachi
 
Constexpr 中3女子テクニック
Constexpr 中3女子テクニックConstexpr 中3女子テクニック
Constexpr 中3女子テクニックGenya Murakami
 
リバースエンジニアリングのための新しいトレース手法 - PacSec 2010
リバースエンジニアリングのための新しいトレース手法 - PacSec 2010リバースエンジニアリングのための新しいトレース手法 - PacSec 2010
リバースエンジニアリングのための新しいトレース手法 - PacSec 2010Tsukasa Oi
 
JavaScriptクイックスタート
JavaScriptクイックスタートJavaScriptクイックスタート
JavaScriptクイックスタートShumpei Shiraishi
 
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド規格書で読むC++11のスレッド
規格書で読むC++11のスレッドKohsuke Yuasa
 
C++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISるC++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISるHideyuki Tanaka
 
A Multiple Pairs Shortest Path Algorithm 解説
A Multiple Pairs Shortest Path Algorithm 解説A Multiple Pairs Shortest Path Algorithm 解説
A Multiple Pairs Shortest Path Algorithm 解説Osamu Masutani
 
型プロファイラ:抽象解釈に基づくRuby 3の静的解析
型プロファイラ:抽象解釈に基づくRuby 3の静的解析型プロファイラ:抽象解釈に基づくRuby 3の静的解析
型プロファイラ:抽象解釈に基づくRuby 3の静的解析mametter
 
php7's ast
php7's astphp7's ast
php7's astdo_aki
 
AtCoder Beginner Contest 007 解説
AtCoder Beginner Contest 007 解説AtCoder Beginner Contest 007 解説
AtCoder Beginner Contest 007 解説AtCoder Inc.
 
初心者講習会資料(Osaka.R#7)
初心者講習会資料(Osaka.R#7)初心者講習会資料(Osaka.R#7)
初心者講習会資料(Osaka.R#7)Masahiro Hayashi
 
C++0x 言語の未来を語る
C++0x 言語の未来を語るC++0x 言語の未来を語る
C++0x 言語の未来を語るAkira Takahashi
 
Scala による自然言語処理
Scala による自然言語処理Scala による自然言語処理
Scala による自然言語処理Hiroyoshi Komatsu
 
PHP AST 徹底解説
PHP AST 徹底解説PHP AST 徹底解説
PHP AST 徹底解説do_aki
 

Similar a Iterators must-go(ja) (20)

2016年第二回プレ卒研in山口研
2016年第二回プレ卒研in山口研2016年第二回プレ卒研in山口研
2016年第二回プレ卒研in山口研
 
Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~
 
DTrace for biginners part(2)
DTrace for biginners part(2)DTrace for biginners part(2)
DTrace for biginners part(2)
 
Enumはデキる子 ~ case .Success(let value): ~
 Enumはデキる子 ~ case .Success(let value): ~ Enumはデキる子 ~ case .Success(let value): ~
Enumはデキる子 ~ case .Success(let value): ~
 
中3女子でもわかる constexpr
中3女子でもわかる constexpr中3女子でもわかる constexpr
中3女子でもわかる constexpr
 
続・ゲンバのSwift
続・ゲンバのSwift続・ゲンバのSwift
続・ゲンバのSwift
 
Constexpr 中3女子テクニック
Constexpr 中3女子テクニックConstexpr 中3女子テクニック
Constexpr 中3女子テクニック
 
リバースエンジニアリングのための新しいトレース手法 - PacSec 2010
リバースエンジニアリングのための新しいトレース手法 - PacSec 2010リバースエンジニアリングのための新しいトレース手法 - PacSec 2010
リバースエンジニアリングのための新しいトレース手法 - PacSec 2010
 
JavaScriptクイックスタート
JavaScriptクイックスタートJavaScriptクイックスタート
JavaScriptクイックスタート
 
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
 
C++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISるC++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISる
 
A Multiple Pairs Shortest Path Algorithm 解説
A Multiple Pairs Shortest Path Algorithm 解説A Multiple Pairs Shortest Path Algorithm 解説
A Multiple Pairs Shortest Path Algorithm 解説
 
型プロファイラ:抽象解釈に基づくRuby 3の静的解析
型プロファイラ:抽象解釈に基づくRuby 3の静的解析型プロファイラ:抽象解釈に基づくRuby 3の静的解析
型プロファイラ:抽象解釈に基づくRuby 3の静的解析
 
php7's ast
php7's astphp7's ast
php7's ast
 
AtCoder Beginner Contest 007 解説
AtCoder Beginner Contest 007 解説AtCoder Beginner Contest 007 解説
AtCoder Beginner Contest 007 解説
 
初心者講習会資料(Osaka.R#7)
初心者講習会資料(Osaka.R#7)初心者講習会資料(Osaka.R#7)
初心者講習会資料(Osaka.R#7)
 
C++0x 言語の未来を語る
C++0x 言語の未来を語るC++0x 言語の未来を語る
C++0x 言語の未来を語る
 
C言語講習会2
C言語講習会2C言語講習会2
C言語講習会2
 
Scala による自然言語処理
Scala による自然言語処理Scala による自然言語処理
Scala による自然言語処理
 
PHP AST 徹底解説
PHP AST 徹底解説PHP AST 徹底解説
PHP AST 徹底解説
 

Más de Akira Takahashi (20)

Cpp20 overview language features
Cpp20 overview language featuresCpp20 overview language features
Cpp20 overview language features
 
Cppmix 02
Cppmix 02Cppmix 02
Cppmix 02
 
Cppmix 01
Cppmix 01Cppmix 01
Cppmix 01
 
Modern C++ Learning
Modern C++ LearningModern C++ Learning
Modern C++ Learning
 
cpprefjp documentation
cpprefjp documentationcpprefjp documentation
cpprefjp documentation
 
C++1z draft
C++1z draftC++1z draft
C++1z draft
 
Boost tour 1_61_0 merge
Boost tour 1_61_0 mergeBoost tour 1_61_0 merge
Boost tour 1_61_0 merge
 
Boost tour 1_61_0
Boost tour 1_61_0Boost tour 1_61_0
Boost tour 1_61_0
 
error handling using expected
error handling using expectederror handling using expected
error handling using expected
 
Boost tour 1.60.0 merge
Boost tour 1.60.0 mergeBoost tour 1.60.0 merge
Boost tour 1.60.0 merge
 
Boost tour 1.60.0
Boost tour 1.60.0Boost tour 1.60.0
Boost tour 1.60.0
 
Boost container feature
Boost container featureBoost container feature
Boost container feature
 
Boost Tour 1_58_0 merge
Boost Tour 1_58_0 mergeBoost Tour 1_58_0 merge
Boost Tour 1_58_0 merge
 
Boost Tour 1_58_0
Boost Tour 1_58_0Boost Tour 1_58_0
Boost Tour 1_58_0
 
C++14 solve explicit_default_constructor
C++14 solve explicit_default_constructorC++14 solve explicit_default_constructor
C++14 solve explicit_default_constructor
 
C++14 enum hash
C++14 enum hashC++14 enum hash
C++14 enum hash
 
Multi paradigm design
Multi paradigm designMulti paradigm design
Multi paradigm design
 
Start Concurrent
Start ConcurrentStart Concurrent
Start Concurrent
 
Programmer mind
Programmer mindProgrammer mind
Programmer mind
 
Boost.Study 14 Opening
Boost.Study 14 OpeningBoost.Study 14 Opening
Boost.Study 14 Opening
 

Iterators must-go(ja)

  • 1. Iterators Must Go Andrei Alexandrescu ©2009 Andrei Alexandrescu
  • 2. This Talk • The STL • Iterators • Range-based design • Conclusions ©2009 Andrei Alexandrescu
  • 4. STLとは何か? • アルゴリズムとデータ構造の(良い|悪い|醜い)ライブラリ • iterators = gcd(containers, algorithms); • Scrumptious Template Lore(すばらしいテンプレートの知恵) • Snout to Tail Length(先頭から最後尾までの長さ) ©2009 Andrei Alexandrescu
  • 5. STLとは何かというと • STLでは答えよりも質問の方が重要 • “基本的なコンテナとアルゴリズムの中で最も一般的な 実装はどのようなものでしょうか?” • ほかのものは全てその余波 • STLで最も重要なこと: STLはひとつの答えではあるが、答えではない ©2009 Andrei Alexandrescu
  • 6. STLは非直観的 • 相対性理論が非直観的なのと同じ方向 • 複素数が非直観的なのと同じ方向 n√-1形式の値は”虚数”だが 方程式で使用することができる。 ©2009 Andrei Alexandrescu
  • 7. 非直観的 • “私は最も一般的なアルゴリズムを設計したい。” • “もちろんできる。あなたが確実に必要とするものは、イテレータと呼ばれ るものである。正確に言うとそれらのうちの5つ。” • 証拠:どんな言語も”たまたま”STLをサポートしなかった。 • 無慈悲な”機能戦争”にもかかわらず • C++とDが唯一 • どちらもSTLのサポートを積極的に目指した • 結果:STLは、C++とDの外から理解するのが非常に困難になった。 ©2009 Andrei Alexandrescu
  • 8. Fundamental vs Incidental in STL (基礎 vs 偶発的なSTL) • F:アルゴリズムは可能な限り狭いインタフェースで定義される • F:アルゴリズムに必要とされる広いイテレータカテゴリ • I:iterator primitiveの選択 • I:iterator primitiveの構文 ©2009 Andrei Alexandrescu
  • 9. STL:良い点 • 正しい質問ができる • 一般的 • 効率的 • 無理なく拡張可能 • 組み込み型に統合された ©2009 Andrei Alexandrescu
  • 10. STL:悪い点 • ラムダ関数のサポートが貧弱 • STLの問題ではない • 高い機会費用 • いくつかのコンテナはサポートできない – たとえば、sentinel-terminatedコンテナ – たとえば、分散ストレージを持つコンテナ • いくつかの反復法はサポートできない ©2009 Andrei Alexandrescu
  • 11. STL:醜い点 • for_each等の試みは助けにならなかった • ストリームによる統合が希薄 • 1単語:allocator • イテレータの最低なところ – 冗長 – 安全ではない – 貧弱なインタフェース ©2009 Andrei Alexandrescu
  • 13. Iterators Rock • コンテナとアルゴリズム間の相互作用をまとめ上げる • “強度低下:”m・nの代わりにm+nを実装 • 拡張可能:ここで、STLが日の目を見て以来、イテレータに動 揺が走った ©2009 Andrei Alexandrescu
  • 14. 危険信号 #1 • 2001年頃にC++ Users Journalは広告キャンペーンを行った – 「CUJに記事を投稿してください!」 – 「英文学を専攻している必要はありません! エディタで始めてください!」 – 「私たちはセキュリティ、ネットワーク技術、C++技術、その他の技術に 興味があります!」 注:まだ他のイテレータには興味がありませんでした • 公開されたイテレータはどれくらい生き残った? ©2009 Andrei Alexandrescu
  • 15. 危険信号 #2 • およそ1975年のファイルコピー #include <stdio.h> int main() { int c; while ((c = getchar()) != EOF) putchar(c); return errno != 0; } ©2009 Andrei Alexandrescu
  • 16. 危険信号 #2 • 20年ほど早送りして… #include <iostream> #include <algorithm> #include <iterator> #include <string> using namespace std; int main() { copy(istream_iterator<string>(cin), istream_iterator<string>(), ostream_iterator<string>(cout,"¥n")); } ©2009 Andrei Alexandrescu
  • 19. 危険信号 #3 • イテレータは定義するのが非常に難しい • かさばった実装と多くの了解事項(gotcha) • Boostはイテレータの定義を支援する完全なライブラリを含ん でいる • 本質的なプリミティブは…3つ? – 終了(At end) – アクセス(Access) – 衝突(Bump) ©2009 Andrei Alexandrescu
  • 20. 危険信号 #4 • イテレータはポインタの構文とセマンティクスを使用する • 勝利/敗北のポインタとの統合 • しかし、これはイテレーションの方法を制限する – パラメータを持った++が必要なので、深いツリーを歩かせることがで きない – OutputIteratorはひとつの型しか受け取ることができない: それらは全て同じ場所へ行くが、output_iteratorは出力するそれぞれ の型でパラメータ化されなければならない ©2009 Andrei Alexandrescu
  • 21. 最終的な命取り • 全てのイテレータプリミティブは基本的に安全ではない • ほとんどのイテレータは、与えられたイテレータのために – 比較できるかどうかを記述することができない – インクリメントできるかどうかを記述することができない – 間接参照できるかどうかを記述することができない • 安全なイテレータを書くことができる – 高いサイズ+速度のコストで – たいていは、設計をカットできなかったというよい議論 (訳注:かなり怪しい訳) ©2009 Andrei Alexandrescu
  • 23. Enter Range • これらの不便さを部分的に回避するため、 Rangeが定義された • Rangeは、begin/endイテレータの組をパックしたもの • そのため、Rangeはより高レベルのcheckable invariant(訳注: チェック可能な不変式?)を持つ • BoostとAdobeのライブラリはRangeを定義した ©2009 Andrei Alexandrescu
  • 26. ほら、どこにもイテレータがない! • イテレータの代わりにRangeをイテレーション用の 基本構造として定義してはどうだろう? • Rangeは、イテレーションに依存しない基本処理を定義するべき • 今後イテレータはなく、Rangeだけが残るだろう(訳注:怪しい) • Rangeはどのプリミティブをサポートしなければならないか begin/endがオプションではないことを思い出してほしい 人々が個々のイテレータを貯め込んでいたら、我々は振り出しに戻らなければならない ©2009 Andrei Alexandrescu
  • 27. Rangeの定義 • <algorithm>は全てRangeで定義でき、他のアルゴリズムも同様にRangeで 実装できる • Rangeプリミティブは、少ないコストでチェック可能でなければならない • イテレータより非効率であってはならない ©2009 Andrei Alexandrescu
  • 28. Input/Forward Range template<class T> struct InputRange { bool empty() const; void popFront(); T& front() const; }; ©2009 Andrei Alexandrescu
  • 29. 証明可能? template<class T> struct ContigRange { bool empty() const { return b >= e; } void popFront() { assert(!empty()); ++b; } T& front() const { assert(!empty()); return *b; } private: T *b, *e; }; ©2009 Andrei Alexandrescu
  • 30. 検索 // オリジナル版のSTL template<class It, class T> It find(It b, It e, T value) { for (; b != e; ++b) if (value == *b) break; return b; } ... auto i = find(v.begin(), v.end(), value); if (i != v.end()) ... ©2009 Andrei Alexandrescu
  • 31. 設計質問 • Rangeでのfindはどのように見えなければならないか? 1. 範囲のひとつの要素(見つかった場合)、 もしくは0個の要素(見つからなかった場合)を返す? 2. 見つかった要素以前のRangeを返す? 3. 見つかった要素以降のRangeを返す? • 正解:(もしあった場合)見つかった要素で始まるRangeを返し、そうでなければ空 を返す なぜ? ©2009 Andrei Alexandrescu
  • 32. 検索 // Rangeを使用 template<class R, class T> R find(R r, T value) { for (; !r.empty(); r.popFront()) if (value == r.front()) break; return r; } ... auto r = find(v.all(), value); if (!r.empty()) ... ©2009 Andrei Alexandrescu
  • 33. エレガントな仕様 template<class R, class T> R find(R r, T value); 「frontがvalueと等しいか、rが使い尽くされるまで、 左から範囲rを縮小する」 ©2009 Andrei Alexandrescu
  • 34. Bidirectional Range template<class T> struct BidirRange { bool empty() const; void popFront(); void popBack(); T& front() const; T& back() const; }; ©2009 Andrei Alexandrescu
  • 35. Reverse Iteration template<class R> struct Retro { bool empty() const { return r.empty(); } void popFront() { return r.popBack(); } void popBack() { return r.popFront(); } E<R>::Type& front() const { return r.back(); } E<R>::Type& back() const { return r.front(); } R r; }; template<class R> Retro<R> retro(R r) { return Retro<R>(r); } template<class R> R retro(Retro<R> r) { return r.r; // klever(訳注:clever? : 賢いところ) } ©2009 Andrei Alexandrescu
  • 36. find_endはどうだろう? template<class R, class T> R find_end(R r, T value) { return retro(find(retro(r)); } • rbegin, rendは必要ない • コンテナは、範囲を返すallを定義する • 後ろにイテレートする:retro(cont.all()) ©2009 Andrei Alexandrescu
  • 37. イテレータでのfind_endは最低だ // reverse_iteratorを使用したfind_end template<class It, class T> It find_end(It b, It e, T value) { It r = find(reverse_iterator<It>(e), reverse_iterator<It>(b), value).base(); return r == b ? e : --r; } • Rangeが圧倒的に有利:はるかに簡潔なコード • 2つを同時に扱うのではなく、1つのオブジェクトのみから構 成されるので、容易な構成になる ©2009 Andrei Alexandrescu
  • 38. さらなる構成の可能性 • Chain : いくつかのRangeをつなげる 要素はコピーされない! Rangeのカテゴリは、全てのRangeの中で最も弱い • Zip : 密集行進法(lockstep)でRangeを渡る Tupleが必要 • Stride : 一度に数ステップ、Rangeを渡る イテレータではこれを実装することができない! • Radial : 中間(あるいはその他のポイント)からの距離を増加させる際に Rangeを渡る ©2009 Andrei Alexandrescu
  • 39. 3つのイテレータの関数はどうだろうか? template<class It1, class It2> void copy(It1 begin, It1 end, It2 to); template<class It> void partial_sort(It begin, It mid, It end); template<class It> void rotate(It begin, It mid, It end); template<class It, class Pr> It partition(It begin, It end, Pr pred); template<class It, class Pr> It inplace_merge(It begin, It mid, It end); ©2009 Andrei Alexandrescu
  • 40. 「困難なところにはチャンスがある。」 "Where there's hardship, there's opportunity." – I. Meade Etop ©2009 Andrei Alexandrescu
  • 41. 3-legged algos ⇒ mixed-range algos template<class R1, class R2> R2 copy(R1 r1, R2 r2); • 仕様:r1からr2へコピーして、手をつけていないr2を返す vector<float> v; list<int> s; deque<double> d; copy(chain(v, s), d); ©2009 Andrei Alexandrescu
  • 42. 3-legged algos ⇒ mixed-range algos template<class R1, class R2> void partial_sort(R1 r1, R2 r2); • 仕様:最も小さな要素がr1の中で終了するように、r1とr2の連結を 部分的にソートする • あなたは、vectorとdequeをとり、両方の中で最も小さな要素を配列に入 れることができる vector<float> v; deque<double> d; partial_sort(v, d); ©2009 Andrei Alexandrescu
  • 43. ちょっと待って、まだある vector<double> v1, v2; deque<double> d; partial_sort(v1, chain(v2, d)); sort(chain(v1, v2, d)); • アルゴリズムは今、余分な労力なしで任意のRangeの組み合わせにおい ても途切れることなく動作することができる • イテレータでこれを試してみて! ©2009 Andrei Alexandrescu
  • 44. ちょっと待って、さらにある vector<double> vd; vector<string> vs; // 密集行進法(lockstep)で2つをソートする sort(zip(vs, vd)); • Rangeコンビネータは、無数の新しい使い方ができるようになる • イテレータでも理論上は可能だが、(再び)構文が爆発する ©2009 Andrei Alexandrescu
  • 45. Output Range • ポインタ構文からの解放されたので、異なる型をサポートすることが可能になった struct OutRange { typedef Typelist<int, double, string> Types; void put(int); void put(double); void put(string); }; ©2009 Andrei Alexandrescu
  • 46. stdinからstdoutにコピーする話に戻ろう #include <...> int main() { copy(istream_range<string>(cin), ostream_range(cout, "¥n")); } 1 • 最後にもう一歩:1行(one-line)に収まる寸言 (one-liner) • ostream_rangeにはstringを指定する必要はない 1 スライド制限にもかかわらず ©2009 Andrei Alexandrescu
  • 47. Infinite Range(無限の範囲) • 無限についての概念はRangeでおもしろくなる • ジェネレータ、乱数、シリーズ、… はInfinite Range • 無限は、5つの古典的カテゴリとは異なる特性; あらゆる種類のRangeが無限かもしれない • ランダムアクセスなRangeさえ無限かもしれない! • 無限について静的に知っておくことは、アルゴリズムを助ける ©2009 Andrei Alexandrescu
  • 48. has_size • Rangeが効率的に計算されたsizeを持っているかどうかは他の独立した特 性 • 索引項目:list.size, 永遠の討論 • Input Rangeさえ、既知のサイズを持つことができる (たとえば、100個の乱数をとるtake(100, rndgen)) – rが無限の場合、take(100, r)は100の長さ – rが長さを知っている場合、長さはmin(100, r.size()) – rが未知の長さで有限の場合、未知の長さ ©2009 Andrei Alexandrescu
  • 49. 予想外の展開(A Twist) • Rangeで<algorithm>をやり直すことができる? • Dのstdlibは、std.algorithmとstd.rangeモジュールで<algorithm>のスー パーセットを提供している(googleで検索してほしい) • RangeはD全体に渡って利用されている: アルゴリズム、遅延評価、乱数、高階関数、foreach文… • いくつかの可能性はまだ簡単には挙げられなかった – たとえばfilter(input/output range) • Doctor Dobb‘s Journalの「The Case for D」をチェックしておいてください。 coming soon... ©2009 Andrei Alexandrescu
  • 50. 結論 • Rangeは優れた抽象的概念である • より良いチェック能力(まだ完全ではない) • 容易な構成 • Rangeに基づいた設計は、イテレータに基づいた関数をはるかに超えるも のを提供する • 一歩進んだSTLによる刺激的な開発 ©2009 Andrei Alexandrescu