Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Constexpr 中3女子テクニック

24,537 views

Published on

  • Be the first to comment

Constexpr 中3女子テクニック

  1. 1. constexpr中3女子テクニック―実践と濫⽤そしてC++14へBoost.勉強会 #12bolero_MURAKAMI2013/6/22
  2. 2. ◆自己紹介• 名前 : 村上 原野 (むらかみ げんや)@bolero_MURAKAMI, id:boleros• 棲息地: 大都会岡山• 仕事 : 猪風来美術館陶芸指導員・普段はろくろをまわしたり、縄文土器をつくったりしています・趣味は constexpr です
  3. 3. ◆自己紹介• 公開しているライブラリ:Sprout C++ Library (constexpr ライブラリ)github.com/bolero-MURAKAMI/Sprout• 前回発表資料:Boost.勉強会 #7【中3⼥⼦でもわかる constexpr】Boost.勉強会 #8【中3⼥⼦が狂える本当に気持ちのいい constexpr】www.slideshare.net/GenyaMurakami
  4. 4. ◆アジェンダ• はじめに• いまさら聞けない constexpr 入門• 逆引き constexpr マニュアル• constexpr アルゴリズム実装テクニック• これからの constexpr の話• まとめ
  5. 5. ◆いまさら聞けない constexpr 入門• constexpr とは• constexpr を使うべき 5 の理由• constexpr の落とし⽳
  6. 6. ◆C++ プログラミングのレイヤープリプロセス時の世界(魔界)コンパイル時の世界(ライブラリアンが多数棲息)実⾏時の世界(人間界)[プログラマのすること][処理されるもの]ソースコードプリプロセッサメタプログラミングテンプレートメタプログラミング型実⾏時オブジェクト定数式通常のプログラミングC++03
  7. 7. ◆C++ プログラミングのレイヤープリプロセス時の世界(魔界)コンパイル時の世界(ライブラリアンが多数棲息)実⾏時の世界(人間界)[プログラマのすること][処理されるもの]constexprソースコードプリプロセッサメタプログラミングテンプレートメタプログラミング型実⾏時オブジェクト定数式通常のプログラミングC++11
  8. 8. ◆constexpr で扱えるデータ[リテラル型][スカラ型] [リテラル型の配列]LiteralType [N][リテラル型への参照]LiteralType const&[算術型][整数型] int, unsigned int, char, ...[浮動小数点型] float, double, ...[ポインタ型][ポインタ] int const*, int (*)(void), ...[メンバポインタ] int T::*, int (T::*)(void), ...[列挙型] enum特定の条件を満たすユーザ定義クラス
  9. 9. ◆constexpr を使うべき 5 の理由• 明示的なコンパイル時定数の定義• コンパイル時定数を返す関数• 副作⽤がないことを保証する• better TMP• 初期化をあらかじめ⾏っておく
  10. 10. ◆明示的なコンパイル時定数の定義• 定数だがコンパイル時定数ではない例– ill-formed !!!struct X { int n; };const X x = { 10 };int a[x.n] = { 1 };配列の宣⾔にはコンパイル時定数が必要
  11. 11. ◆明示的なコンパイル時定数の定義• constexpr で、実⾏時定数ではなくコンパイル時定数であることを明示するstruct X { int n; };constexpr X x = { 10 };int a[x.n] = { 1 };OK! コンパイル時定数
  12. 12. ◆コンパイル時定数を返す関数• コンパイル時に定まる値だがコンパイル時定数として使えない例template<class T, size_t N>struct MyArray {T elem[N];size_t size() const { return N; }};N はコンパイル時定数だが、size() はコンパイル時定数でない
  13. 13. ◆コンパイル時定数を返す関数• constexpr で、関数をコンパイル時定数として使えるようにするtemplate<class T, size_t N>struct MyArray {T elem[N];constexpr size_t size() const { return N; }};OK! コンパイル時定数
  14. 14. ◆副作⽤がないことを保証する• 副作⽤があるか無いか分からない例int x = format();C:ドライブをフォーマットする副作⽤があるかもしれない
  15. 15. ◆副作⽤がないことを保証する• 副作⽤が確実にない例constexpr int x = format();C:ドライブをフォーマットするとしても、その前にコンパイルエラーになる
  16. 16. ◆better TMP• 煩雑なテンプレートメタプログラミングtypedef boost::mpl::vector_c<int, 1, 2, 3, 4, 5> src;typedef typename boost::mpl::accumulate<src,boost::mpl::int_<0>,boost::mpl::plus<boost::mpl::_1, boost::mpl::_2>>::type accumulated;
  17. 17. ◆better TMP• ⾒慣れたプログラムと変わらない constexprconstexpr auto src = make_array<int>( 1, 2, 3, 4, 5 );constexpr auto accumulated =range::accumulate(src, 0, plus<>() );
  18. 18. ◆初期化をあらかじめ⾏っておく• コンパイル時三角関数テーブルusing namespace sprout::adaptors;constexpr array<double, 90> degree_sin_table= sinusoidal( 1. / 360 ) | copied;浮動小数点数のコンパイル時計算が出来るのは constexpr だけ
  19. 19. ◆初期化をあらかじめ⾏っておく• constant initialization– あらゆる動的初期化より先に⾏われるため、ありがちな静的変数の初期化順序への依存や競合が起こらないstatic std::mutex m; // 普通のグローバル変数constexpr コンストラクタを持つ型
  20. 20. ◆constexpr の落とし⽳• 定数式だけど定数じゃない• 実は副作⽤を禁止できない
  21. 21. ◆定数式だけど定数じゃない• すごく const っぽいシグネチャconstexpr int const& f( int const& t );
  22. 22. ◆定数式だけど定数じゃない• すごく const っぽくないシグネチャにしてみるconstexpr int& f( int& t );完全に合法
  23. 23. ◆定数式だけど定数じゃない• const_cast で const 外しをしてみるconstexpr int& f( int const& t ) {return const_cast<int&>(t);}完全に合法
  24. 24. ◆定数式だけど定数じゃない• constexpr 指定の変数が暗黙の const 修飾されるだけで、constexpr 関数の引数や返値の const 性とは何も関わりがない• たとえコンパイル時の定数式評価であっても、非 constrvalue/lvalue 参照などの、あらゆる value categoryの式が扱われる
  25. 25. ◆実は副作⽤を禁止できない• 人生、宇宙、すべての答えを代入するだけの関数template<class T>T& f( T&& t ) { return t = 42; }もちろん副作⽤がある
  26. 26. ◆実は副作⽤を禁止できない• これもそのまま constexpr 関数に出来るtemplate<class T>constexpr T& f( T&& t ) { return t = 42; }
  27. 27. ◆実は副作⽤を禁止できない• これもそのまま constexpr 関数に出来るtemplate<class T>constexpr T& f( T&& t ) { return t = 42; }constexpr int k = f(0);もちろんコンパイル時に評価しようとするとエラーになるが……int i = 0;int j = f( i );実⾏時評価だと何も問題なくコンパイル・実⾏できる
  28. 28. ◆実は副作⽤を禁止できない• 引数によって副作⽤があったり無かったりする場合template<class T>constexpr T& f( T&& t, bool cond ) { return cond ? t : (t = 42); }constexpr int k = f( 0, true ); /* 副作用なし */int i = 0;int j = f( i, false ); /* 副作用あり */
  29. 29. ◆実は副作⽤を禁止できない• constexpr 関数が副作⽤について保証するのは以下の場合しかない– コンパイル時に呼び出されたとき、副作⽤があればコンパイルエラーになる– コンパイル時に呼び出せる(副作⽤がない)とき、実⾏時にそれと等値な引数で呼び出しても、同じく副作⽤がない• 実⾏時の constexpr 関数呼び出しで、副作⽤がないことを証明するには以下の場合しかない– コンパイル時に等値な引数で呼び出して、エラーにならないことを確認する– 実装を⾒て、全ての実⾏パスで副作⽤が起こりえないことを検証する
  30. 30. ◆実は副作⽤を禁止できない• constexpr 関数はコンパイル時にも実⾏時にも呼び出せるが、実⾏時に副作⽤がないことはまったく保証しない• constexpr 指定の有無が、ドキュメントレベルの目安にはなる
  31. 31. ◆逆引き constexpr マニュアル• constexpr で文字列を扱いたい• constexpr で配列(コンテナ)を扱いたい• constexpr でタプルを扱いたい• constexpr でアルゴリズムを使いたい• constexpr で RangeAdaptor を使いたい• constexpr でアサーションを使いたい• constexpr で数学関数を使いたい• constexpr で乱数を使いたい• constexpr でハッシュ関数を使いたい• constexpr で UUID を使いたい• constexpr で構文解析したい• constexpr でレイトレーシングしたい• constexpr で波形編集したい
  32. 32. ◆文字列を扱いたい• それ Sprout.String で出来るよ!• リテラルから文字列クラスへ• 文字列連結• std::string 互換のインタフェース#include <sprout/string.hpp>constexpr auto s = to_string( "Hello world!" );// -> string<12> :型には要素数(最大文字数)が含まれるconstexpr auto s = to_string( "Hello" ) + to_string( "world!" );// string<5> + string<7> -> string<12>constexpr auto pos = s.find( "o wo" ); // 4constexpr auto s2 = s.substr( 0, pos ); // "Hell"
  33. 33. ◆文字列を扱いたい• 文字列→算術型の変換• 算術型→文字列の変換• その他、辞書順⽐較やストリーム入出⼒ etc...constexpr auto istr = to_string( 37564 ); // "37564"constexpr auto dstr = to_string( 3.141592 ); // "3.141592"constexpr auto i = stoi( to_string( "37564" ) ); // 37564constexpr auto d = stod( to_string( "3.141592" ) ); // 3.141592
  34. 34. ◆文字列を扱いたい• 所有権を持たない文字列クラス– (C++1y の std::string_view 互換)#include <sprout/utility/string_view.hpp>constexpr auto s = string_view( "Hello world!" );// -> string_view :型には要素数を含まない
  35. 35. ◆配列(コンテナ)を扱いたい• それ Sprout.Array で出来るよ!• 配列の作成• std::array 互換のインタフェース• constexpr なイテレータ#include <sprout/array.hpp>constexpr auto a = make_array<int>( 1, 2, 3, 4, 5 );// -> array<int, 5>constexpr auto auto size = a.size(); // 5constexpr auto i2 = a[2]; // 3constexpr auto i5 = a.at(5); // error: out_of_rangeconstexpr auto first = a.begin();constexpr auto last = a.end();
  36. 36. ◆タプルを扱いたい• それ Sprout.Tuple で出来るよ!• タプルの作成• std::tuple 互換のインタフェース• ファンクタを呼ぶ#include <sprout/tuple.hpp>constexpr auto t = make_tuple( 10, 3.14, to_string( "Foo" ) );// -> tuple<int, double, string<3>>constexpr auto t = make_tuple( 10, 3.14 );constexpr auto f = make_fused( plus<>() );constexpr auto result = f(t); // call: 10 + 3.14constexpr auto size = tuple_size<decltype(t)>::value; // 3constexpr auto i1 = get<1>(t); // 3.14
  37. 37. ◆アルゴリズムを使いたい• それ Sprout.Algorithm で出来るよ!• 変更を伴わないアルゴリズム– (STL アルゴリズム互換)#include <sprout/algorithm.hpp>constexpr auto a = make_array<int>( 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 );static_assert(is_sorted( a.begin(), a.end() ),"ソートされているか" );static_assert(all_of( a.begin(), a.end(), bind2nd( modulus<>(), 2 ) ),"奇数であるか" );
  38. 38. ◆アルゴリズムを使いたい• 変更を伴うアルゴリズム• 変更を伴うアルゴリズムの STL との違い– 出⼒イテレータを取るアルゴリズムは、代わりに出⼒コンテナを受け取って結果を返す– 入出⼒イテレータのペアを取るアルゴリズムは、代わりに入出⼒コンテナを受け取って結果を返すconstexpr auto a = make_array<int>( 5, 1, 9, 4, 8, 2, 7, 3, 10, 6 );constexpr auto sorted = sort( a ); // 1 2 3 4 5 6 7 8 9 10constexpr auto reversed = reverse( sorted ); // 10 9 8 7 6 5 4 3 2 1std::reverse_copy( first, last, out ); // ↓constexpr auto reversed = reverse_copy( first, last, container );std::reverse( first, last ); // ↓constexpr auto reversed = reverse( container );
  39. 39. ◆アルゴリズムを使いたい• Range 版アルゴリズムもあるよ!#include <sprout/range/algorithm.hpp>constexpr auto a = make_array<int>( 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 );static_assert(is_sorted( a ),"ソートされているか" );static_assert(all_of( a, bind2nd( modulus<>(), 2 ) ),"奇数であるか" );
  40. 40. ◆RangeAdaptor を使いたい• それ Sprout.Range.Adaptor で出来るよ!• 様々なアダプタをパイプ演算⼦で繋げる• Haskell で書くと#include <sprout/range/adaptor.hpp>using namespace sprout::adaptor;constexpr array<int, 10> a =counting( 1 ) // [1..] の無限リスト| transformed( bind2nd( multiplies<>(), 2 ) ) // 全要素に (* 2) 適用| taken( 5 ) // 5 要素を取り出し| jointed( adaptor::counting( 11 ) ) // [11..] をリスト連結| copied // 任意のコンテナへ変換可能にする;// 2 4 6 8 10 11 12 13 14 15(++ [11..]) $ take 5 $ map (* 2) [1..]
  41. 41. ◆RangeAdaptor を使いたい• RangeAdaptor の特⻑– 遅延評価が出来る– 一時オブジェクトの生成を最小限に出来る– アダプタの適⽤が副作⽤を持たない– constexpr に向いている!
  42. 42. ◆アサーションを使いたい• それ Sprout.Assert で出来るよ!• コンパイル時でも実⾏時でも使えるアサート• コンパイル時の場合→コンパイルエラー (GCC)• 実⾏時の場合→標準の assert と同じ (GCC)#include <sprout/assert.hpp>template<typename T>constexpr T div(T num, T denom) {return SPROUT_ASSERT(denom != 0), (num / denom);}div(3.14, 0.0);
  43. 43. ◆アサーションを使いたい• コンパイル時の場合→コンパイルエラー (GCC)• 実⾏時の場合→標準の assert と同じ (GCC)in constexpr expansion of ‘sprout::detail::assertion_check((denom != 0.0),((const char*)"***** Internal Program Error - assertion (denom != 0) failed:a.cpp(6)"))’***** Internal Program Error - assertion (denom != 0) failed: a.cpp(6)
  44. 44. ◆数学関数を使いたい• それ Sprout.Math で出来るよ!• <cmath> の殆どの数学関数をサポート• GCC のビルトイン関数が使える環境ならより高速に#include <sprout/math.hpp>constexpr auto v1 = cos(0.5); // 三角関数constexpr auto v2 = tgamma(3.0); // ガンマ関数
  45. 45. ◆乱数を使いたい• それ Sprout.Random で出来るよ!• 乱数種• いくつかの疑似乱数生成エンジン• いくつかの分布クラス#include <sprout/random.hpp>constexpr auto rng1 = minstd_rand0( seed ); // 線形合同法エンジンconstexpr auto rng2 = taus88( seed ); // 結合トーズワース法エンジンconstexpr std::size_t seed = SPROUT_UNIQUE_SEED;// コンパイル日時、ファイル名、行数を利用constexpr auto dist1 = uniform_int_distribution<>( 1, 6 ); // 整数一様分布constexpr auto dist2 = normal_distribution<>( 5, 2 ); // 正規分布
  46. 46. ◆乱数を使いたい• 返値は { 生成値, 次の状態の生成器 } のペアになる• 乱数列を ForwardTraversalRange として扱うconstexpr auto rnd = dist(rng); // 乱数生成constexpr auto val = rnd.generated_value(); // 生成値constexpr auto gen = rnd.next_generator(); // 次の生成器constexpr auto seq = random::make_range( rng, dist ); // 乱数列constexpr array<int, 10> a = seq | adaptor::copied;
  47. 47. ◆ハッシュ関数を使いたい• それ Sprout.Functional.Hash で出来るよ!• std::hash 互換のインタフェース#include <sprout/functional/hash.hpp>constexpr std::size_t h = hash<double>( 3.14 );
  48. 48. ◆ハッシュ関数を使いたい• Sprout.Checksum でハッシュアルゴリズムも使えるよ!• MD5 や SHA1 のハッシュ関数• メソッドチェインでソースを流し込む#include <sprout/checksum.hpp>constexpr auto md5_hash = md5().process_range( to_string( "foobar" ) )();constexpr auto sha1_hash = sha1().process_range( to_string( "foobar" ) )();
  49. 49. ◆UUID を使いたい• それ Sprout.Uuid で出来るよ!• boost::uuids::uuid 互換のインタフェース#include <sprout/uuid.hpp>
  50. 50. ◆UUID を使いたい• 文字列から UUID 生成• ランダムな UUIDv4 生成• MD5/SHA1 による UUIDv3/v5 生成constexpr auto id = uuids::make_uuid(to_string( "{550e8400-e29b-41d4-a716-446655440000}" ) );// 文字列と同じ UUIDconstexpr auto id = uuids::make_uuid4( SPROUT_UNIQUE_SEED );// 任意の乱数種または乱数生成器による UUIDv4constexpr auto id = uuids::make_uuid5_dns( to_string( "boost.org" ) );// DNS 名前空間の SHA1 による UUIDv5
  51. 51. ◆UUID を使いたい• UUID ユーザ定義リテラルconstexpr auto id3 = "{550e8400-e29b-41d4-a716-446655440000}"_uuid;constexpr auto id5 = "DNS"_uuid5( to_string( "boost.org" ) );
  52. 52. ◆構文解析したい• そうだね、Sprout.Weed だね!!• 例)C++の識別⼦(予約済み識別⼦を除く)にマッチするパーザを作成する#include <sprout/weed.hpp>constexpr auto identifier_p =!( _ >> char_( "A-Z“ ) ) // アンダースコア+大文字で始まらない>> char_( "a-zA-Z_“ ) // 英字またはアンダースコアで始まる>> *lim<15>( char_( "0-9a-zA-Z_“ ) - "__“ )// ゼロ個以上の英数字またはアンダースコア(アンダースコアは連続しない);
  53. 53. ◆構文解析したい• パーザの適⽤constexpr auto s1 = to_string( "_foobar" );static_assert( parse_range( s1, identifier_p ).current() == s1.end(),"マッチする" );constexpr auto s2 = to_string( "_Foobar" );static_assert( parse_range( s2, identifier_p ).current() != s2.end(),"マッチしない" );constexpr auto s3 = to_string( "foo__bar" );static_assert( parse_range( s3, identifier_p ).current() != s3.end(),"マッチしない" );
  54. 54. ◆レイトレーシングしたい• 中3⼥⼦のマストアイテム Sprout.Darkroom#include <sprout/darkroom.hpp>
  55. 55. ◆レイトレーシングしたい• 鏡面や複数光源の設定
  56. 56. ◆レイトレーシングしたい• 合わせ鏡のような再帰的追跡
  57. 57. ◆レイトレーシングしたい• テクスチャファイルの読み込み・貼り付け
  58. 58. ◆波形編集したい• ナウなヤングにバカウケ Sprout.Compost• 単純な正弦波の生成#include <sprout/compost.hpp>using namespace sprout::compost;constexpr array<complex<double>, 256> src =waves::sinusoidal( 10. / 256, 10000. ) | ranges::copied;
  59. 59. ◆波形編集したい• DFT (離散フーリエ変換)constexpr auto trans = src | analyses::dft | ranges::copied;// 離散フーリエ変換constexpr auto spec =trans | analyses::amplitude_spectrum | ranges::copied;// 振幅スペクトルに変換
  60. 60. ◆波形編集したい• 単音の正弦波を生成– 再生:sine_sound.wavusing namespace sprout::compost;constexpr array<std::int16_t, size> wav =waves::sinusoidal( // 指定音階の正弦波を生成equal_temperament_value( semitones ) * base / sample_per_sec, 0.8 )| formats::as_pcm_wave16 // 16bitWAVE に変換| ranges::copied // 任意のコンテナに変換可能にする;
  61. 61. ◆波形編集したい• モーツァルトのきらきら星変奏曲ハ⻑調K. 265の最初の部分のメロディを生成– 再生:sine_twinkle.wav
  62. 62. ◆波形編集したい• 外部ファイルから波形データの読み込み– 再生:sound.wav#define COMPOST_DEF_LOAD_SOURCE_IDENTIFIER wav#define COMPOST_DEF_LOAD_INFO_IDENTIFIER wav_info#define COMPOST_DEF_LOAD_SOURCE_FILE FILENAME#include COMPOST_LOAD_SOURCE
  63. 63. ◆波形編集したい• ディストーションをかける– 再生:distortion.wavconstexpr sprout::array<std::int16_t, size> wav_data =wav.elements()| effects::distorted( 100., 0.5 ) // ディストーション| formats::as_pcm_wave16 // 16bitWAVE に変換| ranges::copied // 任意のコンテナに変換可能にする;
  64. 64. ◆波形編集したい• 音声合成したい– Rosenberg 波を音源波形とする(τ1 = 0.8, τ2 = 0.16)– フォルマント(音声のスペクトルに固有のピーク)を⽤意する– IIR フィルタ(無限インパルス応答において特定の周波数成分を取り出す)でレゾナンスを掛ける– 再生:vowel.wav
  65. 65. ◆constexpr アルゴリズム実装テクニック• 再帰深度を抑える• アルゴリズム• クラス設計• その他
  66. 66. ◆再帰深度を抑える• 再帰深度を抑える意義• IndexTuple イディオム• 倍分再帰
  67. 67. ◆再帰深度を抑える意義• constexpr 関数においてループは再帰で実装できるが、その深度は実装によって制限される• 規格が実装に推奨する constexpr 関数の再帰深度 512– 数千要素の配列を、線形再帰でループするとすぐ制限を超える– ちなみにテンプレートインスタンス化の再帰深度 1024
  68. 68. ◆再帰深度を抑える意義• 重要なのは計算量(オーダー)– 線形オーダー Ο(n) だと厳しい– 対数オーダー Ο(log n) だとかなり良い(例えば要素数 16→256でも再帰数 4→8 )– 定数オーダー Ο(1) なら最高(要素数が増えても変わらない)
  69. 69. ◆IndexTuple イディオム• IndexTuple イディオムで reverse の実装template<class T, size_t N, ptrdiff_t... Indexes>constexpr array<T, N>reverse_impl( array<T, N> const& arr, index_tuple<Indexes...> ) {return array<T, N>{{ arr[N-1-Indexes]... }};}template<class T, size_t N>constexpr array<T, N> reverse( array<T, N> const& arr ) {return reverse_impl( arr, typename make_index_tuple<N>::type() );}
  70. 70. ◆IndexTuple イディオム• IndexTupe イディオムで reverse の実装template<class T, size_t N, ptrdiff_t... Indexes>constexpr array<T, N>reverse_impl( array<T, N> const& arr, index_tuple<Indexes...> ) {return array<T, N>{{ arr[N-1-Indexes]... }};}template<class T, size_t N>constexpr array<T, N> reverse( array<T, N> const& arr ) {return reverse_impl( arr, typename make_index_tuple<N>::type() );}インデックス列のパラメータパックパック展開式
  71. 71. ◆IndexTuple イディオム• 原理– 型レベルで [0..N) のインデックス列を⽤意(テンプレートパラメータパック)– 要素列 a[0], a[1], ..., a[N-1] に展開(パック展開式)• 数学的意味– 簡単に⾔えば自然数列 {0, 1, ..., N-1} から S への写像a:{0, 1, ..., N-1} → S を定義すること– reverse の場合は、入⼒を A とすると一般項 a[n] = A[N-1-n]になる
  72. 72. ◆IndexTuple イディオム• 適⽤できる条件– 入⼒が RandomAccessTraversal である(添字アクセス可能)– 一般項の計算量がほぼ定数時間である– 漸化式(例えばフィボナッチ数)などは一般に項 a[n] の計算量がΟ(n) になるので適⽤できない– n 番目の素数は一意に定まるが、n 番目の素数を定数時間で求める方法はないので適⽤できない• 計算量– 再帰深度は定数オーダー Ο(1)– ただし、テンプレートインスタンス化の再帰深度が高々対数オーダー Ο(log n)
  73. 73. ◆IndexTuple イディオム• ライブラリ– C++14 の integer_sequence• N3658 の提案では、Efficiency considerations として計算量Ο(log2 n) の実装について考察– Sprout の index_tuple• integer_sequence とほぼ互換のインタフェース• 様々なユーティリティ• 計算量 log2(n) の保証
  74. 74. ◆倍分再帰• 倍分再帰で distance の実装template<typename InputIterator>constexpr pair<InputIterator, typename iterator_traits<InputIterator>::difference_type>distance_impl_1(pair<InputIterator, typename iterator_traits<InputIterator>::difference_type> const& current,InputIterator last, typename iterator_traits<InputIterator>::difference_type n){typedef pair<InputIterator, typename iterator_traits<InputIterator>::difference_type> type;return current.first == last ? current: n == 1 ? type(next(current.first), current.second + 1): distance_impl_1(distance_impl_1(current,last, n / 2 /* 左側を検索 */),last, n - n / 2 /* 右側を検索 */);}
  75. 75. ◆倍分再帰• 倍分再帰で distance の実装template<typename InputIterator>constexpr pair<InputIterator, typename iterator_traits<InputIterator>::difference_type>distance_impl(pair<InputIterator, typename iterator_traits<InputIterator>::difference_type> const& current,InputIterator last, typename iterator_traits<InputIterator>::difference_type n){typedef pair<InputIterator, typename iterator_traits<InputIterator>::difference_type> type;return current.first == last ? current: distance_impl(distance_impl_1(current,last, n /* 検索範囲 n を検索 */),last, n * 2 /* 次の検索範囲 n * 2 を検索 */);}
  76. 76. ◆倍分再帰• 倍分再帰で distance の実装template<typename InputIterator>constexpr typename iterator_traits<InputIterator>::difference_typedistance(InputIterator first, InputIterator last) {typedef pair<InputIterator, typename iterator_traits<InputIterator>::difference_type> type;return distance_impl(type(first, 0), last, 1).second;}
  77. 77. ◆倍分再帰• 原理– サイクル 1 :与えられた範囲を⼆分検索する– サイクル 2 :試⾏範囲を倍々しながらサイクル 1 を呼ぶ
  78. 78. ◆倍分再帰• 基本的には⼆分検索(サイクル 1)• ただし、範囲が未知なので試⾏範囲を指数的に増やしていく(サイクル 2)– 現在の範囲で終了しなかったら、右側に同じだけ拡張• サイクル 1 は Ο(log n)、サイクル 2 も Ο(log n) なので、合わせた計算量も Ο(log n) になる• 試⾏範囲を「⼆倍→⼆分」繰り返すので、「倍分再帰」と名付けた(造語)
  79. 79. ◆倍分再帰• 適⽤できる条件– 入⼒が SinglePassTraversal 以上である• 計算量– 再帰深度はほぼ対数オーダー Ο(log2 n)– ただしイテレータ同士の⽐較回数が最大で Ο(2^ceil(log2 n))• STL で同等のアルゴリズムだと Ο(n)• n が 2 の冪乗の場合は同じ計算量だが、それ以外だと若⼲非効率
  80. 80. ◆アルゴリズム• 変更を伴わないアルゴリズム• 変更を伴うアルゴリズム• RangeAdaptor
  81. 81. ◆変更を伴わないアルゴリズム• <algorithm> や <numeric> の Non-modifyingsequence operations– all_of, find など• <cstring>– strlen, strcmp など• インタフェース– 標準ライブラリとほぼ同じにできる
  82. 82. ◆変更を伴わないアルゴリズム• 処理をディスパッチ– 入⼒が RandomAccessTraversal である• → 単純な⼆分検索– それ以外• → 倍分再帰• 計算量– 入⼒が一つの範囲の場合 → Ο(log2 n)– 入⼒が⼆つの範囲の場合 → Ο(log2 n+m)– 標準ライブラリとほぼ同じ効率で実装できる
  83. 83. ◆変更を伴うアルゴリズム• <algorithm> や <numeric> の Mutating sequenceoperations– reverse, transform, sort など• インタフェース– 出⼒をコンテナにコピーして返すような、副作⽤のない形にする必要があるvoid sort( Iterator first, Iterator last );// 副作用があるconstexpr Container sort( Container const& cont );// 副作用がない
  84. 84. ◆変更を伴うアルゴリズム• 出⼒が添字に対して一意に定義できる場合– reverse など– 入⼒が RandomAccessTraversal である• → IndexTuple イディオム– それ以外• → 愚直に線形再帰• 出⼒が添字に対して一意に定義できない場合– sort など– 愚直に線形再帰
  85. 85. ◆変更を伴うアルゴリズム• 計算量– IndexTuple イディオムが使える場合• → Ο(n)– それ以外• → アルゴリズムによる• 問題点– クイックソートなど In-place アルゴリズムが使えない– 出⼒を常にコピーするため、オブジェクトコピーの計算量がΟ(n) 余計に必要
  86. 86. ◆RangeAdaptor• Boost.Range や Pstade.Oven の実装が典型的• RangeAdaptor の適⽤自体は通常副作⽤を持たず、最終的に評価される時点で実処理が⾏われる
  87. 87. ◆RangeAdaptor• C++ における Range は、しばしば [first, last) といったイテレータを組にしたクラスとして表現される– Range のクラスそのものは状態を変更しないことが多い– D ⾔語の Range では、イテレータではなく Range そのものが状態を持っているstruct MyRange {constexpr iterator begin() const;constexpr iterator end() const;};
  88. 88. ◆RangeAdaptor• 要素コピー回数の⽐較• 通常のアルゴリズムの場合– コピー回数 = 10 × 3• RangeAdaptor の場合– コピー回数 = 10constexpr auto a1 = iota<array<int, 10>>( 1 ); // 10回constexpr auto a2 = reverse( a1 ); // 10回constexpr auto a = range::transform(a2, a2, bind2nd( multiplies<>(), 2 ) ); // 10回constexpr array<int, 10> a =counting( 1, 10 )| reversed| transformed( bind2nd( multiplies<>(), 2 ) )| copied;
  89. 89. ◆RangeAdaptor• reversed アダプタの実装例template<class BaseRange>class reversed_range {constexpr reverse_iterator begin() const;constexpr reverse_iterator end() const;};class reversed_forwarder {};static constexpr reversed_forwarder reversed = {};template<class BaseRange>constexpr reversed_range<BaseRange>operator| ( BaseRange const& rng, reversed_forwarder ) {return reversed_range<BaseRange>( rng );}
  90. 90. ◆RangeAdaptor• reversed アダプタの実装例template<class BaseRange>class reversed_range {constexpr reverse_iterator begin() const;constexpr reverse_iterator end() const;};class reversed_forwarder {};static constexpr reversed_forwarder reversed = {};template<class BaseRange>constexpr reversed_range<BaseRange>operator| ( BaseRange const& rng, reversed_forwarder ) {return reversed_range<BaseRange>( rng );}reversed 自体は機能を持たず、operator|() を ADL で呼ぶ為のタグとして使われるreversed_range は、範囲を逆順に辿るイテレータを保持するoperator|() によって、reversed_range を生成して返す
  91. 91. ◆RangeAdaptor• 目的の動作をするイテレータさえ実装できれば、RangeAdaptor も簡単に実装できる
  92. 92. ◆クラス設計• イテレータインタフェース• データメンバアクセス• メソッドチェイン
  93. 93. ◆イテレータインタフェース• 通常副作⽤を持たない– デリファレンス: *a– 添字デリファレンス: a[n]– 加算/減算: a + n, a - n, a - b– 等価⽐較: a == b, a != b– 不等価⽐較: a < b, a > b, a <= b, a >= b• 通常副作⽤を持つ– インクリメント/デクリメント: ++a, --a, a++, a--– 加算代入/減算代入: a += n, a -= n
  94. 94. ◆イテレータインタフェース• 案 1 :++/-- の代わりにメンバ関数 next/prev を持たせる– 欠点:同じメンバ関数を持っていない、他の全てのイテレータを統一的に扱えないstruct MyIterator {constexpr MyIterator next() const;constexpr MyIterator prev() const;};
  95. 95. ◆イテレータインタフェース• 案 2 :フリー関数 iterator_next/iterator_prev をADLでルックアップ– 利点:他のライブラリのイテレータでもアダプトすることができる– 欠点:邪悪な ADL に依存する– 名前を next/prev にしない理由は、一般的すぎる名前だと ADLで余計なものまで候補になるおそれがある為• Sprout では案 2 を採⽤しているconstexpr MyIterator iterator_next(MyIterator);constexpr MyIterator iterator_prev(MyIterator);
  96. 96. ◆イテレータインタフェース• 挙動をフォールバックする汎⽤ next フリー関数– ADL で iterator_next(a) が呼べるか• → iterator_next(a)– a が RandomAccessIterator かつリテラル型か• → it + 1– それ以外• → std::next(a)template<class ForwardIterator>constexpr ForwardIterator next(ForwardIterator it);
  97. 97. ◆データメンバアクセス• 通常のメンバ関数では、データメンバアクセスを完全に代替することは出来ない
  98. 98. ◆データメンバアクセス• 公開されたデータメンバの場合template<class T>struct Holder {T value;};constexpr int i = Holder<int>{ 100 }.value; // データメンバを参照
  99. 99. ◆データメンバアクセス• メンバ関数でアクセスする場合– ill-formed !!!template<class T>struct Holder2 {T v_;constexpr T const& value() const { return v_; } /* const版 */constexpr T& value() { return v_; } /* 非const版 */};
  100. 100. ◆データメンバアクセス• メンバ関数でアクセスする場合– ill-formed !!!• 返値型のみが異なるメンバ関数の定義と⾒做され、コンパイルエラーになるtemplate<class T>struct Holder2 {T v_;constexpr T const& value() const { return v_; } /* const版 */constexpr T& value() const { return v_; } /* 非const版 */};constexpr メンバ関数が暗黙で const 修飾される
  101. 101. ◆データメンバアクセス• 非 const 版を constexpr 指定しない方法template<class T>struct Holder2 {T v_;constexpr T const& value() const { return v_; } /* const 版 */T& value() { return v_; } /* 非 const 版 */};
  102. 102. ◆データメンバアクセス• 非 const 版を constexpr 指定しない方法• 非 const な prvalue や rvalue 参照からのメンバ関数呼び出しは、非 const 版が優先されるtemplate<class T>struct Holder2 {T v_;constexpr T const& value() const { return v_; } /* const 版 */T& value() { return v_; } /* 非 const 版 */};constexpr int i = Holder2<int>{ 100 }.value(); // Oops! 非 const 版が呼ばれる
  103. 103. ◆データメンバアクセス• static メンバ関数を使う方法template<class T>struct Holder3 {T v_;static constexpr T const& value(Holder3 const& t) { return t.v_; }/* const 版 */static constexpr T& value(Holder3& t) { return t.v_; }/* 非 const 版 */};
  104. 104. ◆データメンバアクセス• static メンバ関数を使う方法template<class T>struct Holder3 {T v_;static constexpr T const& value(Holder3 const& t) { return t.v_; }/* const 版 */static constexpr T& value(Holder3& t) { return t.v_; }/* 非 const 版 */};constexpr int i = Holder3<int>::value( Holder3<int>{ 100 } ); // OK!かなり書きづらい
  105. 105. template<class T>constexpr T const& value(Holder3<T> const& t) {return Holder3<T>::value(t); /* const版 */}template<class T>constexpr T& value(Holder3<T>& t) {return Holder3<T>::value(t); /* 非 const 版 */}◆データメンバアクセス• フリー関数を使う方法
  106. 106. template<class T>constexpr T const& value(Holder3<T> const& t) {return Holder3<T>::value(t); /* const版 */}template<class T>constexpr T& value(Holder3<T>& t) {return Holder3<T>::value(t); /* 非 const 版 */}◆データメンバアクセス• フリー関数を使う方法constexpr int i = value( Holder3<int>{ 100 } ); // スッキリ!
  107. 107. ◆データメンバアクセス• C++14 では、constexpr メンバ関数が暗黙 const 修飾されなくなる!constexpr int i = Holder3<int>{ 100 }.value();template<class T>struct Holder4 {T v_;constexpr T const& value() const { return v_; } /* const 版 */constexpr T& value() { return v_; } /* 非 const 版 */};こうあって然るべき
  108. 108. ◆メソッドチェイン• メソッドチェインでデータを処理するクラスの例– const 版は次の状態のオブジェクトを返す– 非 const 版は内部状態を変更するstruct Hasher {constexpr Hasher process( Data const& src ) const; /* const 版 */constexpr void process( Data const& src ); /* 非 const 版 */constexpr Hashed finish() const; /* 結果を返す */};
  109. 109. ◆メソッドチェイン• メソッドチェインでデータを処理するクラスの例constexpr auto hashed =Hasher().process(src1).process(src2).finish();
  110. 110. ◆メソッドチェイン• メソッドチェインでデータを処理するクラスの例– ill-formed !!!– constexpr の有無では解決できないconstexpr auto hashed =Hasher().process(src1).process(src2).finish(); 非 const な prvalue や rvalue 参照からのメンバ関数呼び出しは、非 const 版が優先される
  111. 111. ◆メソッドチェイン• const 修飾された prvalue を返す方法struct Hasher {constexpr Hasher const process( Data const& src ) const; /* const 版 */constexpr void process( Data const& src ); /* 非 const 版 */constexpr Hashed finish() const; /* 結果を返す */};const 修飾
  112. 112. ◆メソッドチェイン• const 修飾された prvalue を返す方法– constexpr メンバ関数でメソッドチェインを⾏う場合、返値はconst 修飾されたオブジェクトにすべきconstexpr auto hashed =as_const(Hasher()).process(src1).process(src2).finish(); OK!
  113. 113. ◆その他• ポインタの扱い• 浮動小数点数の扱い
  114. 114. ◆ポインタの扱い• 定数式評価においては、ポインタ型はRandomAccessIterator の仕様を満たさないp1 < p2 // compile error!p1 - p2 // compile error!順序⽐較が出来ないポインタ同士の減算が出来ない
  115. 115. ◆ポインタの扱い• 案 1 : distance(p1, p2) で距離計算する– 毎回線形オーダー Ο(n) の計算を要する– p1 <= p2 であることを保証しなければならない
  116. 116. ◆ポインタの扱い• 案 2 : 基準アドレスと基準からの距離を持ったイテレータでラップする– ⽐較や減算は、予め計算された距離をもとに算出する
  117. 117. ◆ポインタの扱い• 案 2 : 基準アドレスと基準からの距離を持ったイテレータでラップする– ⽐較や減算は、予め計算された距離をもとに算出する– それ Sprout でできるよ!#include <sprout/range.hpp>constexpr int a[2] = { 1, 2 };constexpr auto rng = range::make_ptr_range( a ); // RandomAccess
  118. 118. ◆浮動小数点数の扱い• 定数式評価で浮動小数点例外を発生する処理はコンパイルエラーになるconstexpr auto NaN = numeric_limits<double>::quiet_NaN();NaN == NaN; // OK. (non-signaling)NaN < NaN; // compile error! (signaling)NaN の大小⽐較など
  119. 119. ◆浮動小数点数の扱い• 浮動小数点数を扱う定数式で NaN に対応させたかったらまず最初に NaN チェック• GCC のビルトイン数学関数は定数式として使えるが、規格上浮動小数点例外が発生する呼出はコンパイルエラーになる– 例えば NaN や ±∞ などの極値を返すような呼出– Sprout.Math の数学関数は常に non-signaling な実装なので、常に定数式で使える
  120. 120. ◆これからの constexpr の話• C++14 で変わる constexpr• C++14 constexpr の使⽤例• 更にこれからの constexpr
  121. 121. ◆C++14 で変わる constexpr• ローカル変数の使⽤• 副作⽤の許可• 制御構文の使⽤• void がリテラル型に• メンバ関数の暗黙の const の撤廃• 抽象マシンモデルの採⽤• 標準ライブラリ
  122. 122. ◆ローカル変数の使⽤• ローカル変数の使⽤constexpr double heron(double a, double b, double c) {double s = (a+b+c)/2;return sqrt(s*(s-a)*(s-b)*(s-c) );}
  123. 123. ◆副作⽤の許可• 副作⽤の許可• ただし、オブジェクトの寿命が定数式評価中に始まるものでなければならない– 定数式評価の外に副作⽤を及ぼすことは出来ない– 定数式評価全体で副作⽤がないことが保証されるtemplate<class Iterator, class Distance>constexpr void advance( Iterator& it, Distance n ) {it += n;}
  124. 124. ◆制御構文の使⽤• 制御構文の使⽤• if, switch, while, do-while, for, range-based fortemplate<class Range, class T>constexpr T accumulate( Range const& rng, T init ) {for ( auto const& e : rng ) {init += e;}return init;}
  125. 125. ◆void がリテラル型に• void がリテラル型に• 返値が void の関数も constexpr 指定できるtemplate<class Iterator>constexpr void sort( Iterator first, Iterator last );
  126. 126. ◆メンバ関数の暗黙の const の撤廃• メンバ関数の暗黙の const の撤廃• ただし、現時点のドラフトでは標準ライブラリのインタフェースまで更新されていないtemplate<class T, size_t N>struct array {constexpr T const& front() const;constexpr T& front();};
  127. 127. ◆抽象マシンモデルの採⽤• C++11では、constexpr 関数の評価は、関数呼び出しの置換(function invocation substitution)という規則で定義されていた– 関数の評価を、呼び出し先の関数の式の評価と置換することで、関数呼び出しをエミュレートする– 式変形の最適化に近い• C++14では、定数式の評価はC++抽象マシンのサブセットとして再定義される
  128. 128. ◆標準ライブラリ• 新たに constexpr 指定されるもの– forward, move, move_if_noexcept– tuple, pair のほとんどの機能– initializer_list
  129. 129. ◆C++14 constexpr の使⽤例• 可変⻑前方向リスト#include <sprout/forward_clist.hpp>constexpr int f() {using namespace sprout;auto li = forward_clist<int>();{decltype(li)::item it{ 10 };li.push_front(it);// { 10 }{array<decltype(li)::item, 5> its{{ 100, 200, 300, 400, 500 }};li.insert_after(li.begin(), its.begin(), its.end());// { 10, 100, 200, 300, 400, 500 }/* ... */li.unlink(its.begin(), its.end());}li.unlink(it);}}
  130. 130. ◆C++14 constexpr の使⽤例• 可変⻑前方向リスト#include <sprout/forward_clist.hpp>constexpr int f() {using namespace sprout;auto li = forward_clist<int>();{decltype(li)::item it{ 10 };li.push_front(it);// { 10 }{array<decltype(li)::item, 5> its{{ 100, 200, 300, 400, 500 }};li.insert_after(li.begin(), its.begin(), its.end());// { 10, 100, 200, 300, 400, 500 }/* ... */li.unlink(its.begin(), its.end());}li.unlink(it);}}ノードはスタック上に無ければならない(動的オブジェクトが使えない)ので、ローカル変数として宣⾔RAII が使えないので手動でリンク解除が必要
  131. 131. ◆更にこれからの constexpr• C++1y で応⽤される constexpr• C++14 constexpr の問題点• 標準ライブラリの更なる constexpr 化• 期待しうる constexpr の進化
  132. 132. ◆C++1y で応⽤される constexpr• N3627 : switch 文での利⽤– constexpr operator==() で⽐較できるリテラル型を switch文で使えるようにする提案• N3580 : Concept Lite– 型の制約を constexpr 関数で表現する軽量コンセプトの提案
  133. 133. ◆C++14 constexpr の問題点• 例外安全性の問題– ローカル変数と副作⽤が使えるようになったが、ユーザ定義デストラクタが使えない• つまり RAII が使えない– try-catch ブロックが書けない• つまり例外をハンドリングできない– 例外安全でない constexpr 関数を簡単に書くことが出来る– そのような関数を、インタフェースを変えずに例外安全に書き直すのも困難
  134. 134. ◆標準ライブラリの更なる constexpr 化• メンバ関数の暗黙の const 修飾が撤廃されたため、constexpr メンバ関数のオーバーロードは、ほぼ無条件で constexpr 指定を付け加えられると考えられる• イテレータ関連の機能
  135. 135. ◆期待しうる constexpr の進化• ラムダ式– 相変わらずマングルの問題で⾒送られているが、議論は続けられている• new/delete– N3664(new のメモリ確保を実装が省略可能にする)が採択されたので、コンパイル時に処理系がDynamic storage duration 以外の記憶期間を使えるよう変更されれば、コンパイル時 new も許可されるかもしれない
  136. 136. ◆期待しうる constexpr の進化• ユーザ定義リテラル– テンプレートパラメータパックを受け取るユーザ定義文字列リテラル• コンパイル時 IO– 欲しい
  137. 137. ◆まとめ• C++11 のもっとも重要な新機能の一つであるconstexpr は、様々なテクニックを駆使して何でも出来ることが示された• C++14 では、constexpr が書きやすくなり、モデルも刷新され、よりユーザフレンドリに• C++1y 以降では、constexpr の更なる進化が望める• constexpr の今後に大いに期待しながらエンジョイしよう!!
  138. 138. ご清聴ありがとうございました

×