リテラル文字列型までの道
KMC ID:dtyazsk
自己紹介
• ID:dtyazsk (DtとかDYZとかの呼び名がある)
• 理学部新2回生
• 色々やってる
• DTMとか
• プログラミングとか
• お絵描きも(→はtwitterのアイコン)
• C++の闇に囚われかけてる
• 趣味は弾幕STG(東方とか)と音ゲー(BMSとか)
• たまに配信します
はじめに
• この中にはプログラミングしてる人は多くいるであろう
• そして実行時をメインとする人たちがほとんどであるはずである
• そこで私はC++のコンパイル時処理界隈の一端を
紹介すればいいのではと考えた
• まだまだC++初級者の私だが全力でコンパイル時処理に挑戦する
というのは半分冗談で
• 何を思ったか「手探り簡易文字列操作補助的ライブラリ(in C++)」
とかいうわけわからん題名で講座立候補してた
• どうせやるなら実行時でやるよりコンパイル時でやるほうが
講座の題材としては面白い
• ちょうど手元にMinGWがあったのでgcc4.8.1使って遊んだってわけ
• 簡易ライブラリとしてまとめた方が進捗が見やすい(ほんまか)
よくある質問
• Q:なぜお前はコンパイル時にやろうとするのか
よくある質問
• Q:なぜお前はコンパイル時にやろうとするのか
• A:制限された状況下でコードを書くのは楽しいことだと思いませんか
• ゲームの制限プレイなどと同じ
やること一覧
• 前回の復習+必要なC++の仕様の確認
• C++的メタプログラミングとは
• ライブラリの仕様を考えよう
• 汎用的メタ関数を作ろう(type_traits.hpp)
• 強化型index_tupleとは(index_tuple.hpp)
• 配列のラッパーを作る(array.hpp)
• 配列の操作をしよう(array_algorithm.hpp)
• 文字列型を作ろう(basic_string.hpp)
• そしてコンパイル時文字列操作へ…(string_control.hpp)
• 参考資料とまとめ
やること一覧
• 前回の復習+必要なC++の仕様の確認
• C++的メタプログラミングとは
• ライブラリの仕様を考えよう
• 汎用的メタ関数を作ろう(type_traits.hpp)
• 強化型index_tupleとは(index_tuple.hpp)
• 配列のラッパーを作る(array.hpp)
• 配列の操作をしよう(array_algorithm.hpp)
• 文字列型を作ろう(basic_string.hpp)
• そしてコンパイル時文字列操作へ…(string_control.hpp)
• 参考資料とまとめ
templateの復習と追加説明
• 型や整数定数などを取ってコンパイル時に展開し
それぞれで新しいクラスや関数を作る機能
• クラスはテンプレート変数に制限加えた状態を指定し特殊化できる
• 例えばtemplate<class Type>class hogeがあるとしてhoge<int>で別実装とか
• 一方関数は完全な特殊化は可能
• 部分特殊化は無理
• クラスの何かがテンプレート引数依存でメンバもテンプレートのとき
そのメンバのテンプレート引数を明示的に渡す場合は
templateキーワードが必要
constexprの確認
• コンパイル時定数作ったり定数式作ったりするキーワード
• 定数式の実装は実質return文一つ
• classが条件を満たせばリテラル型と扱われconstexprで使える
• 別の非volatileなリテラル型のみをメンバ変数に持ち、「aggregateである」か
「1つ以上のムーブでもコピーでもないconstexprコンストラクタと
トリビアルなデストラクタ(要はユーザー定義されてない)を持つ」
• intなどの基本データ型はリテラル型、要はこれらから再帰的に定義される
• aggregateの条件は後に載せる
• templateなconstexpr宣言された関数について、constexpr関数の
制約に合わないインスタンス化された場合ill-formedにはならないが
普通の関数として扱われる
• constexprメンバ関数は暗黙でconst修飾される(後に問題になる)
可変長引数テンプレート
• “…”によりテンプレート引数を固めたり展開したりする機能
• template<class Type,class… Types>Type sum(Types… args)
{
Type ret(0);
for ( const auto& x : { args… } )
ret += x;
return ret;
}
を例に説明しよう
可変長引数テンプレート
• “…”によりテンプレート引数を固めたり展開したりする機能
• template<class Type,class… Types>Type sum(Types… args)
{
Type ret(0);
for ( const auto& x : { args… } )
ret += x;
return ret;
}
を例に説明しよう
• 可変長引数テンプレートの宣言には…をつける(ここではclassのあと)
可変長引数テンプレート
• “…”によりテンプレート引数を固めたり展開したりする機能
• template<class Type,class… Types>Type sum(Types… args)
{
Type ret(0);
for ( const auto& x : { args… } )
ret += x;
return ret;
}
を例に説明しよう
• 可変長引数に渡された引数は一つに(ここではTypesに)固められる
可変長引数テンプレート
• “…”によりテンプレート引数を固めたり展開したりする機能
• template<class Type,class… Types>Type sum(Types… args)
{
Type ret(0);
for ( const auto& x : { args… } )
ret += x;
return ret;
}
を例に説明しよう
• 固めたTypesは…で展開される
可変長引数テンプレート
• “…”によりテンプレート引数を固めたり展開したりする機能
• template<class Type,class… Types>Type sum(Types… args)
{
Type ret(0);
for ( const auto& x : { args… } )
ret += x;
return ret;
}
を例に説明しよう
• 関数の引数の型としてTypesを使った場合関数の引数も固められる
可変長引数テンプレート
• “…”によりテンプレート引数を固めたり展開したりする機能
• template<class Type,class… Types>Type sum(Types… args)
{
Type ret(0);
for ( const auto& x : { args… } )
ret += x;
return ret;
}
を例に説明しよう
• argsに固められた引数はやはり…で展開できる
可変長引数テンプレート
• “…”によりテンプレート引数を固めたり展開したりする機能
• template<class Type,class… Types>Type sum(Types… args)
{
Type ret(0);
for ( const auto& x : { args… } )
ret += x;
return ret;
}
を例に説明しよう
• 可変長引数テンプレートにも型推論は適用される
何に使うの
• 可変長引数の関数を作るのに使える
• argsをそのまま別の関数に投げることもできる
• 整数型可変長引数の展開で配列でホゲホゲできたりする(後述)
展開の仕方にも色々ある
• 例えばtemplate<size_t… Nums>があるとして0,1,2,3を渡したとする
• size_t x[]={ Nums… }でsize_t x[]={ 0 , 1 , 2 , 3 } に展開される
• size_t y[]={ x[Nums]… }でsize_t y[]={ x[0] , x[1] , x[2] , x[3] }に
• size_t z[]={ (4 + Nums)… }でsize_t z[]={ 4 , 5 , 6 , 7 }に展開される
• size_t w[]={ func( Nums )… }で
size_t w[]={ func(0), func(1), func(2), func(3) }
2つ以上あっても問題ない時がある
• 例えばtemplate<size_t… Num>class hoge;と
template<class,class>class piyo;があるとして、その特殊化
template<size_t… NumsA,size_t… NumsB>
class piyo<hoge<NumsA…>,hoge<NumsB…>>はwell-formed
• 他にもあった気がしますが今回使わないので省略
SFINAEという仕様
• Substitution Failure Is Not An Errorの略
• 日本語に訳すなら「置き換え失敗はコンパイルエラーでない」
• 要はtemplateの置き換えが失敗しても
即座にコンパイルエラーにはならない機能
• 具体例は後であげる
その他雑多なこと
using エイリアス
• using hoge = piyo;でhogeがpiyoと同じになる
• templateも可能(template<int N>using hoge = piyo<N>;など)
static_assert
• コンパイル時整数定数と文字列リテラルを渡して使う
• 整数定数がfalseなら文字列を出力してコンパイルエラーになる
typename
• templateをほげほげするコードだと型であるか分からない時がある
• これをつけておくと「型である」とコンパイラに伝えられる
やること一覧
• 前回の復習+必要なC++の仕様の確認
• C++的メタプログラミングとは
• ライブラリの仕様を考えよう
• 汎用的メタ関数を作ろう(type_traits.hpp)
• 強化型index_tupleとは(index_tuple.hpp)
• 配列のラッパーを作る(array.hpp)
• 配列の操作をしよう(array_algorithm.hpp)
• 文字列型を作ろう(basic_string.hpp)
• そしてコンパイル時文字列操作へ…(string_control.hpp)
• 参考資料とまとめ
Template-MetaProgramming(TMP)とは?
• templateの特殊化などを利用して再帰を回したりして行うメタプログ
ラミングの一種
• 例えばこんなの(コンパイル時に階乗を求めるコード)
template<size_t N>struct factorial
{static constexpr int value = N * factorial<N-1>::value;};
template<>struct factorial<0>
{static constexpr int value = 1;};
TMPで他にできること
• 可変長引数テンプレートと組み合わせてこんなことも
• 複数の型と整数定数を渡してその番号の型を返すクラス
template<size_t N , class Type , class… Types>struct test
{using type = test<N-1,Types…>::type;};
template<class Type,class… Types>struct test<0,Type,Types…>
{using type = Type;};
constexprコンパイル時処理
• 定数式を使ったコンパイル時処理
• リテラル型やらなにやらを使い、時にはTMPのテクニックも使う
• 条件演算子は永遠の友達
• どこらへんがメタプログラミングなのかさっぱりわからないが
とりあえずここに載せておく
TMPとconstexprで注意すべき点
• 規格が推奨している再帰深度の限界値が存在する
• templateは1024、constexprは512
• 再帰深度線形オーダーの実装だと案外あっさりオーバーしたりする
• 出来る限り再帰深度は対数オーダーになるように実装したい
• それとメモリにも限界はあるので参照渡しするなりして極力減らす
• が、後半メモリをムシャムシャするコードが出てきたり
やること一覧
• 前回の復習+必要なC++の仕様の確認
• C++的メタプログラミングとは
• ライブラリの仕様を考えよう
• 汎用的メタ関数を作ろう(type_traits.hpp)
• 強化型index_tupleとは(index_tuple.hpp)
• 配列のラッパーを作る(array.hpp)
• 配列の操作をしよう(array_algorithm.hpp)
• 文字列型を作ろう(basic_string.hpp)
• そしてコンパイル時文字列操作へ…(string_control.hpp)
• 参考資料とまとめ
どういうライブラリにするか
• とりあえずコンパイル時に簡単な文字列操作ができるライブラリ
であることは絶対条件
• そのために必要な機能も実装する必要がある
• 実装は全てヘッダで行う
• 処理にリンカーを介するタイミングが存在しないため
• constexpr関数は暗黙でinlineになるので問題ない
• 標準ライブラリは一切使用しない
• これを決めたタイミングでは使用してる場面が無かったため
• ちなみに後に使う必要が発生したためそれは自分で実装します
最低限必要なもの
type_traits.hpp
• 最低限の型操作
index_tuple.hpp
• 可変長引数テンプレートにまつわるイディオム
array.hpp array_algorithm.hpp
• 配列のラッパー型とそれに施せるアルゴリズム
basic_string.hpp
• 文字列型クラスとその基礎的なoperatorオーバーロードなど
string_control.hpp
• 目標
その他雑多なこと
• Dyz_Compiletime_Lib、略してDCLとする
• namespaceは当然dcl
• utility.hppでsize_tを定義
• 標準ライブラリを使わないので自分で定義しないとコンパイラに怒られる
• Pairっぽい何かいると思って作ったけど今回使わなかった
• それとmin関数も作っておく
• 実装の補助になる部分はnamespace detailで隠蔽
• インテリセンス的機能を使った時、補助の型とか邪魔になるし
• なんか色々定義してたのに使わないの多すぎて泣ける
やること一覧
• 前回の復習+必要なC++の仕様の確認
• C++的メタプログラミングとは
• ライブラリの仕様を考えよう
• 汎用的メタ関数を作ろう(type_traits.hpp)
• 強化型index_tupleとは(index_tuple.hpp)
• 配列のラッパーを作る(array.hpp)
• 配列の操作をしよう(array_algorithm.hpp)
• 文字列型を作ろう(basic_string.hpp)
• そしてコンパイル時文字列操作へ…(string_control.hpp)
• 参考資料とまとめ
type_traits.hppでやりたいこと
• 型を利用したホゲホゲを扱う
• さっき言ってたSFINAEを活用するようなものも作る
• 以降、テンプレート引数を取りtypeで型を返したり
static constexprなメンバ定数valueで整数値を返したりする
関数のようなものをメタ関数と呼ぶことにします
enable_if
• 例えばこんな状況がある
• 「テンプレート引数Nが偶数なら実装A、奇数なら実装Bで使いたい」
• そういやSFINAEとかあったな…
enable_if
• テンプレート引数としてFlagとTypeをとり
Flagがtrueならusing type = Typeする(falseならtypeは存在しない)
• こうすることでFlag==falseなら置き換え失敗が起きる
• enable_if<false,Type>::typeは存在しないので
enable_if
• 素直な実装
template<bool,class>struct enable_if{};
template<class Type>struct enable_if<true,Type>
{
using type = Type;
};
enable_if
• どうやって使うの
• 例えばこんな使い方がある
template<size_t N>typename enable_if<(N%2==0),bool>::type
hoge(){return true;}
template<size_t N>typename enable_if<!(N%2==0),bool>::type
hoge(){return false;}
• enable_ifを使うのにtypenameがいるのは第1引数の値によってそも
そもtypeが存在しない場合があるため
• Nが奇数なら上はtypeが存在しないので置き換え失敗
• Nが偶数なら下はtypeが存在しないので置き換え失敗
• これで条件によって実装分岐が出来た
2つが同じ型かチェックしたい
• 型Aと型Bは同じかチェックしたい
• operator==は当然使えない
• 同じならテンプレート引数一つで済む→特殊化可能
• よってこう
template<class,class>struct is_same{static constexpr bool value = false};
template<class Ty>struct is_same<Ty,Ty>
{static constexpr bool value = true;}:
やること一覧
• 前回の復習+必要なC++の仕様の確認
• C++的メタプログラミングとは
• ライブラリの仕様を考えよう
• 汎用的メタ関数を作ろう(type_traits.hpp)
• 強化型index_tupleとは(index_tuple.hpp)
• 配列のラッパーを作る(array.hpp)
• 配列の操作をしよう(array_algorithm.hpp)
• 文字列型を作ろう(basic_string.hpp)
• そしてコンパイル時文字列操作へ…(string_control.hpp)
• 参考資料とまとめ
index_tupleって?
• 整数型の可変長引数テンプレートを取るテンプレートクラスがあると
する(別にテンプレート関数でもいい)
• 0から9とかの連続した値をぶっこみたいと考える
• Nを渡したら0からN-1まで自動に渡したクラスを返して欲しいとか
• さてどうしましょうって話
index_tuple
• とりあえずtemplate<int… Nums>struct index_tuple;を定義する
• テンプレート引数でclassを取るようにテンプレートクラスを定義、
index_tuple<Nums…>で特殊化しその時渡したいクラスの
テンプレート引数にNums…を渡してホゲホゲする
• 何らかのクラスがメンバにusing type = index_tuple<Nums…>を持つ
ようにして先ほどのクラスにtypeを渡せば目的が達成される
直感的に素直な実装
• N-1から0までを順に渡せばいい
• 可変長引数の先頭を取って再帰してくメタ関数があるが
その逆で 先頭に追加していく
template<size_t End,size_t… Nums>struct index_count:
index_count<End-1,End-1,Nums…>{};
template<size_t… Nums>struct index_count<0,Nums…>
{
using type = index_tuple<Nums…>;
};
• しかしこれだと再帰深度が線形オーダーになる
どうすればいいか
• 再帰深度を対数オーダーに抑える方法が知られているのでそれを
紹介する
• 鍵となるのは0からN-1の可変長引数パックNumsがあれば
Nums…,(N+Nums)…は0から2N-1まで展開されること
• index_count<N>がindex_tuple<0,…,N-1>だとしたらindex_count<2N>
とindex_count<2N+1>は再帰深度一つ増やすだけで求めれる
• すなわちNが偶数ならindex_count<N/2>を、奇数なら
index_count<(N-1)/2>を求めてそれを元にindex_count<N>を求める
準備
• 前の結果のindex_tupleを次の最大値をもらってそこから次の
index_tupleを返すメタ関数を作る
template<class Index,int N,bool is_odd>struct index_count_next;
template<int… Nums,int End>
struct index_count_next<index_tuple<Nums…>,End,true>
{using type = index_tuple<Nums…,(End+Nums)…>;};
Template<int… Nums,int End>
struct index_count_next<index_tuple<Nums…>,End,false>
{using type = index_tuple<Nums…,(End+Nums)…,2*End>;};
template<>struct index_count_next<index_tuple<>,0,false>
{using type = index_tuple<0>;};
• 0に繋がるのは1のみであるためこのように特殊化して問題はない
index_countを実装
• index_count<N/2>::typeとindex_count<(N-1)/2>::typeが当初の目的
通りの型になっていると仮定
• この時先ほどのindex_count_nextを使うとindex_count<N>::typeも
目的通りindex_tuple<0,…,N-1>になる
• ここでindex_count<0>::typeをindex_tuple<>とする
• 再帰的に全てのNでindex_count<N>::typeがindex_tuple<0,…,N-1>に
要はこう
constexpr int get_next(int N){return (N%2 == 0 ? N/2 : (N-1)/2);}
template<int N>struct index_count{
using type = index_count_next<
index_count< get_next(N) >::type ,
get_next(N) ,
N%2==0)>::type;
};
template<>struct index_count<0>{
using type = index_tuple<>;
};
index_rangeについて
• 例えば2からN+1の範囲で順に取りたいとする
• ここでもindex_countが役に立つ
すなわち可変長引数パックNumsがあったとき(Nums+2)…で
上の目的は達成される
• 同じようにStartからEndまでStep刻みで用意できるメタ関数があれば
便利では
• StartからEndまでStep刻みだと(End-Start)/Stepに1を加えたものが
要素数となる
じゃあどうするの
• こんな感じでいいっしょ
template<class,int,int>struct index_range_impl;
template<int… Nums,int Step,int Start>struct
index_range_impl<index_tuple<Nums…>,Step,Start>
{using type = index_tuple<(Start*Nums+Step)…>;};
template<int Start,int End,int Step>
using index_range = index_range_impl<typename
index_count< 1 + (End-Start)/Step >::type , Step , Start>;
• enable_if使ってEnd-Start/Step<0の時index_tuple<>を
typeにするのもアリ
やること一覧
• 前回の復習+必要なC++の仕様の確認
• C++的メタプログラミングとは
• ライブラリの仕様を考えよう
• 汎用的メタ関数を作ろう(type_traits.hpp)
• 強化型index_tupleとは(index_tuple.hpp)
• 配列のラッパーを作る(array.hpp)
• 配列の操作をしよう(array_algorithm.hpp)
• 文字列型を作ろう(basic_string.hpp)
• そしてコンパイル時文字列操作へ…(string_control.hpp)
• 参考資料とまとめ
配列のラッパークラス作る
• 関数の返り値で配列をそのまま返すのは無理
• というわけでラッパークラスを作りましょうって話
• ラッパーなので中身にはもちろん静的配列を持つ
• 初期化は配列っぽくしたい
• SIZEが0なら中身空のクラスにする
ところで
• Cの構造体は以下の様な初期化ができる
struct hoge{int a,b,c;};
struct hoge piyo = {0,1,2};
• Cとの互換性を維持するため一定条件を満たせば
C++でもこの初期化はできる
• その条件がaggregateであることである
• え、aggregateって…
aggregateについて
• 以下の条件を満たすのはaggregateである
• 「配列である」か「以下のもの全てを持たないクラス」
• ユーザー定義のコンストラクター
• 非staticのメンバ変数の初期化子
• privateおよびprotectedである非staticなメンバ変数
• 基本クラス
• virtual関数が存在しないもの
• メンバ変数が全てリテラル型であるaggregateはリテラル型であった
• これを使おう
ベースとなるクラス
template<class Ty,size_t SIZE>class array
{
public:
Ty elem_[SIZE];
};
• aggregateの要件を満たし、またTyがリテラル型ならリテラル型になる
• メンバ変数elem_に直接アクセスすれば値をゲットしたりできるがや
はりoperator[]でアクセスしたい
ここで問題が発生する
• operator[]はもちろん参照型を返すべき
• 定数でないときhoge[0]=2のように値を変更したい
• しかしC++11には「constexprメンバ関数は暗黙でconst修飾される」
という仕様が存在する
• メンバ関数のconst修飾→メンバ変数が変更しないことを保証する
• 参照返しの関数がconst修飾されているとき
返り値もconst修飾されている必要がある
• 要は単純にconstexpr Ty& operator[](size_t arg){return elem_[arg];}
だけでは万事解決しない
非constexprを用意すればいいんでねーの
• const参照を返すconstexpr版operator[]と
通常の参照を返すoperator[]の二種類を用意する
• この時constexpr定数に関しても通常の変数に対しても問題はない
(前者はconstexpr版が、後者は通常版が呼ばれる)
• 非リテラル型に対してもconstexprの仕様から
constexpr版が単なるconst修飾の関数になるため良い
• これでいいのでは?
• しかし一つだけ問題があって
コンパイル時の右辺値に対してはこれが使えない
• 右辺値、すなわち関数の返り値はconst修飾されない
でも仕方がないので
• constexprなoperator[]と普通のoperator[]を用意
• 右辺値で使うときはelem_に直接アクセスしてもらう
• ていうかなんでコンパイル時の右辺値にconst修飾なされてないんだろうか
• これで一応配列のラッパーっぽいのは出来た
普通の配列からarrayを作りたい
• arrayの方が扱いやすいし
• C++11ではList-initializationという初期化方法がある
• 例えばhoge{{1,2},3}の用に初期化できる
• これとさっきのindex_countを使えば配列 to arrayの変換できる
実装
template<class,class,size_t>struct array_lap_impl;
template<int… Nums,class Ty,size_t SIZE>
struct array_lap_impl<index_tuple<Nums…>,Ty,SIZE>{
static constexpr array<Ty,SIZE> get(const Ty (&ar)[SIZE])
{ return array<Ty,SIZE>{ar[Nums]…}; } };
template<class Ty,size_t SIZE>
constexpr array<Ty,SIZE> array_lap(const Ty (&ar)[SIZE])
{return array_lap_impl<typename
index_count<SIZE>::type,Ty,SIZE>::get(ar); }
やること一覧
• 前回の復習+必要なC++の仕様の確認
• C++的メタプログラミングとは
• ライブラリの仕様を考えよう
• 汎用的メタ関数を作ろう(type_traits.hpp)
• 強化型index_tupleとは(index_tuple.hpp)
• 配列のラッパーを作る(array.hpp)
• 配列の操作をしよう(array_algorithm.hpp)
• 文字列型を作ろう(basic_string.hpp)
• そしてコンパイル時文字列操作へ…(string_control.hpp)
• 参考資料とまとめ
配列にアルゴリズム?
• 例えば特定の関数を要素全てに施すとか条件を満たす要素の数を
数えるとか
• 2つ以上のarrayを繋げるとかarrayの一部を切り取るとかも
• ソートとかもあるけど今回は使わないので作らないです
• Iterator?いえ、知らない子ですね…(嘘です余裕がありませんでした)
配列を結合したり分割したりしたい
• 例えばコンパイル時マージソートとかする時
分割と結合は欠かせない
• 分割というより特定範囲を切り取る関数array_cutと
• 2つのarrayをくっつけるarray_mergeを定義する
• まずはarray_cutから
配列の一部を切り抜く?
• 例えばarray<int,10> arがあったとして
2番目から6番目を抜き出したいとか考える
• 2,3,4,5,6をint… Numsにまとめれたら
array<int,5>{ar[Nums]…}で目的は達成される
• 特定の範囲を可変長引数にする方法とは…
index_rangeを使おう
template<class>struct array_replace;
template<int… Nums>struct array_replace {
template<class Ty,size_t ArgSIZE,size_t RetSIZE>
static constexpr array<Ty,RetSIZE>
get(const array<Ty,ArgSIZE>& ar)
{return array<Ty,ReSIZE>{ar[Nums]…};}};
template<size_t Start,size_t End,class Ty,size_t SIZE>
constexpr array<Ty,End-Start+1>array_cut(const array<Ty,SIZE>& ar)
{return array_replace<typename
index_range<Start,End>::type>::get<Ty,ArgSIZE,RetSIZE>(ar);}
• Start番目からEnd番目の要素を抜き出す関数
2つのarrayをくっつける?
• array<int,4> arAとarray<int,3> arBをくっつけてarray<int,7> arCを作る
• arC={arA[0], arA[1], arA[2], arA[3], arB[0], arB[1], arB[2]}になる
• 2つのarrayの型が違う場合でもキャストはしたいよね的な
• arAの型にarBをあわせたい
2つのarrayと2つのindex_count
template<class,class>struct array_merge_impl;
template<int… NumsA,int… NumsB>struct array_merge_impl
<index_tuple<NumsA…>,index_tuple<NumsB…>>{
template<class TyA,size_t SIZEA,class TyB,size_t SIZEB>
static constexpr array<TyA,SIZEA+SIZEB>
get(const array<TyA,SIZEA>& arA,const array<TyB,SIZEB>& arB)
{return array<TyA,SIZEA+SIZEB>
{arA[NumsA]…,static_cast<TyA>(arB[NumsB])…};}};
template<class TyA,size_t SIZEA,class TyB,size_t SIZEB>
constexpr array<TyA,SIZEA+SIZEB>
array_merge(const array<TyA,SIZEA>& arA,const array<TyB,SIZEB>& arB)
{return array_merge_impl
<typename index_count<SIZEA>::type, typename index_count<SIZEB>::type>
::get(arA,arB);}
• 一つのスライドに収めるもんじゃねぇ
各要素の関数を施した結果を返したい
• 例えば各要素3倍するとか
• arrayの引数arと関数オブジェクトfuncを参照渡しして
arの各要素をfuncに渡す
• func(ar[Nums])…ってすれば返り値が求まる
• ではどうするか
for_eachの実装
template<class>struct for_each_impl;
template<size_t Nums…>class for_each_impl<index_tuple<Nums…>>{
template<class RetTy,class ArgTy,size_t SIZE,class FuncTy>
constexpr array<RetTy,SIZE>
get(const array<ArgTy,SIZE>& ar,FuncTy& func)
{return array<RetTy,SIZE>{ func(ar[Nums])… };} };
template<class RetTy,class ArgTy,size_t SIZE,class FuncTy>
constexpr array<RetTy,SIZE>
for_each(const array<RetTy,SIZE> &ar,FuncTy& func)
{return for_each_impl<typename index_count<SIZE>::type>
::get<RetTy>(ar,func);}
• 実行時に要素を直接いじるfor_eachも考えたかったけど面倒だった
• decltype使えばいいことに気づいたのは上のコード書いた後だった
ある条件を満たす要素を数えたい
• 例えば整数型が素数かどうか
• チェックする関数を渡して各要素をそれに渡す
• 前から順にチェックしていく実装は誰でも思いつくが
当然線形オーダー
• 要素数を二分割してそれぞれでチェックすれば
対数オーダーになるはず
• 配列を二分割するのではなく
チェック範囲を二分割することで配列の増殖を抑える
こんな感じ
template<class Ty,size_t Num,class FuncTy>
constexpr size_t match_num
(array<Ty,Num>& ar,FuncTy& func,size_t start=0,size_t end=Num-1)
{return (start==end ? static_cast<bool>(func(ar[start])) :
match_num(ar,func,start,(start+end)/2) +
match_num(ar,func,(start+end)/2+1,end));}
• startからendまでチェック
• 分割地点については→
やること一覧
• 前回の復習+必要なC++の仕様の確認
• C++的メタプログラミングとは
• ライブラリの仕様を考えよう
• 汎用的メタ関数を作ろう(type_traits.hpp)
• 強化型index_tupleとは(index_tuple.hpp)
• 配列のラッパーを作る(array.hpp)
• 配列の操作をしよう(array_algorithm.hpp)
• 文字列型を作ろう(basic_string.hpp)
• そしてコンパイル時文字列操作へ…(string_control.hpp)
• 参考資料とまとめ
いよいよ文字列型basic_stringを作る
• 文字型にもいろいろあるのでtemplate引数にする
• charは代表例
• コンパイル時には動的確保ができないのでさっきのarrayに保持する
• したがって文字列サイズもtemplate引数に
• コンストラクタは通常の文字列を渡すのとarrayによるものを用意する
• 前者は当たり前、後者は今後の操作とかのために
• したがってnull文字が末端であることが前提となる
大きさを変えるキャスト?
• 例えばn文字をm文字に変えるキャストを考える
• n>mなら特に考えずm-n-1文字分ぶった切って末端をnullにすればいい
• array_cutとarray_mergeを使えばいい
• n<mのときはどうするべきか
• null文字で埋めた場合後の文字列結合の時面倒になる
• ていうか何で埋めても文字列結合で思ったとおりの結果にならない
• もう面倒なのでn<mのときは考えないことにします
凄まじくテキトーな実装
//basic_string<Ty,SIZE>での実装を考える、保持array名はstr_
template<size_t S>constexpr operator basic_string<Ty,S>
{ return cast_help<S>();}
template<size_t S>constexpr typename
enable_if<(SIZE>S),array<Ty,S>>::type cast_help(){
return array_merge(
array_cut<0,S-2>(str_),array<Ty,1>{static_cast<Ty>(0)} ); }
• キャスト演算子でenable_if使う方法がわからなかったという言い訳
• null文字は文字コード0なので0をTyにキャストします
文字列を結合する
• 2つの文字列を結合させたい
• 文字列aとbをa+bで結合させる
• aの末端はnull文字なのでそれは無視する
• するとこう
template<class T,size_t SA,size_t SB>basic_string<T,SA+SB-1>
operator+(const basic_string<T,SA>& a,const basic_string<T,SB>& b)
{return basic_string<T,SA+SB-1>(array_merge(array_cut<0,SA-2>(a.str_),b.str_));}
ラッパー関数を作りたい
• std::stringにはto_stringとかいう関数がある。それをパクりたい
• size_t to basic_stringのみ作ります
• double to basic_stringは面倒でした
• テンプレート引数で文字列サイズを渡しそれにあうように作る
• 例えばSIZE=4、引数が24なら024みたいな
• ’0’はASCII準拠とする
• 変態じみた文字コードとか考慮に入れてられん
int文字列
• SIZEを引数に取るということは最高SIZE-1桁ということ
• それ以上は捨てます
• 前からN番目はSIZE-N-1桁目であることに注意
• 先頭がSIZE-1桁目を表すので
• 再帰とか考えるの面倒なのでindex_tuple使います
• 実行時に使う時処理が減るだろうって考え
実装
template<class Ty>constexpr Ty get_digit_num(int num,int digit)
{return static_cast<Ty>(static_cast<int>(’0’) +
( (num /pow(10,digit) )%10));
template<class,class,size_t>struct to_string_impl;
template<int… Nums,class Ty,size_t SIZE>
struct to_string_impl<index_tuple<Nums…>,Ty,SIZE>{
static constexpr array<Ty,SIZE> get(const int& num)
{return array<Ty,SIZE>{get_digit_num(num,SIZE-Nums-1)…};}};
• pow(int,int)はmath.hppあたりにでも定義しときましょう
• ‘0’の文字コードを知らなくてもできる実装(速度とか知らん)
2つの文字列は同じかチェックしたい
• 文字列aとbでoperator==したい
• 文字列サイズとか型が違うなら無条件でfalse
• 同じならそれぞれの要素をチェックする
• 前から順にやると再帰深度が線形オーダーになる
• さっきと同じように範囲分割を繰り返します
実装
template<class Ty,size_t SIZE>constexpr bool string_equal_impl
(const array<Ty,SIZE>& a,const array<Ty,SIZE>& b,
size_t start=0,size_t end=SIZE-1){
return ( start==end ? a[start]==b[start] :
string_equal_impl(a,b,start,(start+end)/2))&&
string_equal_impl(a,b,(start+end)/2+1),end);}
template<class TyA,size_t SA,class TyB,size_t SB>constexpr typename
enable_if<(SA==SB)&&(is_same<TyA,TyB>::value),bool>::type
operator==(const basic_string<TyA,SA>& a,
const basic_string<TyB,SB>&b){return string_equal_impl(a,b);}
template<class TyA,size_t SA,class TyB,size_t SB> constexpr typename
enable_if<!((SA==SB)&&(is_same<TyA,TyB>::value)),bool>::type
operator==(const basic_string<TyA,SA>& a,
const basic_string<TyB,SB>&b){return false;}
• 長い、長すぎる
• なおis_sameの出番ここで終了の模様
やること一覧
• 前回の復習+必要なC++の仕様の確認
• C++的メタプログラミングとは
• ライブラリの仕様を考えよう
• 汎用的メタ関数を作ろう(type_traits.hpp)
• 強化型index_tupleとは(index_tuple.hpp)
• 配列のラッパーを作る(array.hpp)
• 配列の操作をしよう(array_algorithm.hpp)
• 文字列型を作ろう(basic_string.hpp)
• そしてコンパイル時文字列操作へ…(string_control.hpp)
• 参考資料とまとめ
簡単な文字列操作
• 例えば文字列置き換えとか
• 今回詳しくやるのは3つ
• 同じ長さの文字列AとBについて引数内のAをBに置き換える
• 違う長さの文字列AとBについて引数内の最初に出るAをBに置き換える
• 文字列に含まれる数字から整数を抜き出す
• 補助として指定の文字列が何文字目から始まっているかも必要
指定の文字列が何番目にあるか
• あるbasic_string型の文字列strがあり、その中に文字列xが含まれる
場合、何文字目から始まるかをチェックしたい
• N文字目から始まるM文字が一致しているかのチェックが必要となる
がそれはarray_cutと==を使えば用意に達成できる
• operator==の再帰深度は対数オーダーなので深いことは考えない
• 同様に開始する文字数も範囲分割法で求めます
• ただしendの初期値はSIZE-Mです(そこから始めてM文字でSIZE-1になる)
• コードあほほど長いので省略します(関数名はstring_first_match)
文字列の一部を置き換える?
• 例えばDtYaZsKの一部を置き換えてDtyAzsKにするとか
• 文字列のN文字目からのM文字をあるL文字に変える関数を考える
• コンパイルタイム限定ならindex_tuple使えば余裕
• int… のテンプレートパラメータを3つ使う
• constexpr関数の引数でNを指定したい
• index_countなどの引数はテンプレート引数なのでそのままでは使えない
ラインタイムで使うための工夫?
• まずNを普通にテンプレート引数にしたverの関数を作る
• それらで関数配列を作る
• index_countのためにクラスのstatic constexprメンバにする
• 関数配列っていうより関数ポインタ配列
• 当然全部インスタンス化されるためメモリムシャムシャだがしかたない
• 条件演算子を使う方法のアウトラインが思いついたが
残念ながらそれは発表に間に合わなかった
その他この置き換えの注意点
• N文字の文字列をM文字に置き換える場合元々の文字列をSIZEとす
れば返り値はSIZE+M-Nで置き換える文字列配列の引数はM+1
• したがって置き換える文字列配列をテンプレート引数でMと指定する
場合返り値のサイズはSIZE+M-N-1
• N-1文字の文字列を置き換えるとしたら返り値はSIZE+M-Nである
文字列に含まれる部分文字列を置換
• 例えば文字列xにDtYaZsKが含まれればそれをplasmaに置換とか
• 返り値の文字列サイズが置換前に確定してる必要があるので先頭
の1個だけです
• 指定文字列(上でいうDtYaZsKにあたるもの)がある場合はいいけどな
い場合はどうしよう
constexpr関数で例外を投げる?
• constexpr関数と条件演算子を組み合わせることで
例外を投げることが可能
• 例えばこんな感じ
constexpr int hoge(int a)throw (sqrt_failed)
{return a>=0 ? sqrt(a) : throw sqrt_failed() }
• 条件演算子の片方が返り値の型に一致してる場合もう片方で例外
をなげる事が可能
• コンパイル時に例外を投げることになったらコンパイルエラーになる
というわけで
• まずはstring_first_matchで置換したい文字列の先頭を探す
• string_first_match返り値がSIZEなら例外を投げとく
• そうでないなら先ほどの置換関数を使って置換する
• そして返す
同じ長さの文字列を置換する
• 例えばxという文字列が与えられたらその中のhogeを全てpiyoにする
• 置換後も同じ長さなら何回置換しても返り値の型は変わらない
• よってさっきの置換関数を何回も繰り返せばよし
• しかし置換する場所をstring_first_matchで求めるとして
それを使いまわしたい
てなわけで
• 使いまわすために関数の引数をローカル変数代わりにする
• ユーザーサイドで呼び出す関数を呼び出す(Aとする)
→補助関数にAの引数に加えstring_match_firstを渡す
→s_m_fの返り値がSIZEでないなら
置換した結果を引数にしてAに渡す、SIZEならそのまま返す
• めっちゃ多い数置換できなさそうだがそればかりは仕方ない
• 一回につき3個再帰するので200は間違いなく無理
• pair利用すればもっと抑えられそうだが面倒で考えるのをやめた
文字列内に含まれる数字から整数を作れ
• 例えば__TIME__から数字を取り出して
コンパイル時乱数のシードにする
• コンパイル時乱数ってなんじゃってツッコミはなし
• 返り値0からスタート、前からチェックしていって
数字なら返り値を10倍してその数を足すってしたい
• やはり範囲分割法でやりたいが
今回はただ普通に範囲分割法を使う訳にはいかない
• “10-2”で8を返すのは別の話になるので他所でやってください
途中結果を引数として渡す工夫
• Is_numとget_numはそれぞれ数字のチェックと数字のゲット
constexpr int string_to_integer(string str,int ret,size_t start,size_t end)
{return start==end ?
(is_num(str.str_[start]) ? 10*ret + get_num(str.str_[start]) : ret)
: string_to_integer(
str , string_to_integer
(str , ret , start , (start+end)/2 ),
(start+end)/2+1,end) );}
他にも作ろうとしたもの
• 文字列逆転
• abcdをdcbaにするみたいな
• index_rangeとarray_replaceとarray_mergeを使ってほげほげ
• 文字列切り取り
• DtYaZsKからYaZsを取り出したいとか
• ポインタとindex_countで揃える
• 文字列一部抹消
• 上の逆でDtYaZsKからDtKを取り出すみたいな
• index_countとindex_rangeを組み合わせて云々
• 置換関数にnull文字だけの配列渡したほうが早い気もする
やること一覧
• 前回の復習+必要なC++の仕様の確認
• C++的メタプログラミングとは
• ライブラリの仕様を考えよう
• 汎用的メタ関数を作ろう(type_traits.hpp)
• 強化型index_tupleとは(index_tuple.hpp)
• 配列のラッパーを作る(array.hpp)
• 配列の操作をしよう(array_algorithm.hpp)
• 文字列型を作ろう(basic_string.hpp)
• そしてコンパイル時文字列操作へ…(string_control.hpp)
• 参考資料とまとめ
constexprまとめ
• 線形探索は範囲分割法を使えば再帰深度を対数オーダーにできる
• 分割範囲はstart to (start+end)/2と(start+end)/2+1 to end
• +で繋げる、&&で繋げる、前半を後半の引数に渡す、など
• templateと例外はうまく使おう
• 特にenable_ifは便利(classの特殊化より色々楽)
• 標準ライブラリのtype_traitsは要注目
• わざわざ苦しんでやるものでもないです
• 素直にclang3.4以降使った方がいい
• C++14のconstexprは手続き型の書き方でいける
今後やりたいことと反省点
• tuple.hppとかmath.hppとか作りたい
• mathの方は作業臭すごそう
• array_algorithm.hppをiteratorさん使う実装に直したい
• iteratorさん作ってると時間がなくなりそうだったから今回は無視した
• まだまだ勉強不足を感じる
• decltypeを使うトリックとか最近知った
• 自分のやる内容はしっかり確認しよう
• 文字列操作が何か未だによくわかってない
参考資料(スライド編)
• ボレロ村上氏のスライド
• 中3女子でもわかる constexpr
http://www.slideshare.net/GenyaMurakami/constexpr-10458089
• 中3女子が狂える本当に気持ちのいい constexpr
http://www.slideshare.net/GenyaMurakami/constexpr-11509325
• constexpr中3女子テクニック
http://www.slideshare.net/GenyaMurakami/constexpr-23355469
• すごいconstexprたのしくレイトレ!
http://www.slideshare.net/GenyaMurakami/constexpr-29223898
• 蔵人紗音のじ氏のスライド
• Template Meta Programming入門から応用まで
https://dl.dropboxusercontent.com/u/43530447/tmp_web.pdf
参考資料(サイトや文献編)
• ENiyGmaA Code http://d.hatena.ne.jp/boleros/
• index_tuple イディオムにおける index_range の効率的な実装
http://d.hatena.ne.jp/boleros/20120406/1333682532
• Wonderlands in Usagi's brain http://blog.wonderrabbitproject.net/
• C++におけるtemplateキーワードの効果の忘却とついでにtypenameについて
http://blog.wonderrabbitproject.net/2013/02/ctemplatetypename.html
• C++11の文法と機能(C++11: Syntax and Feature)
http://ezoeryou.github.io/cpp-book/C++11-Syntax-and-Feature.xhtml
• 一人ぼっちの共鳴 http://dos-sonority.jugem.jp/
• 今更人に聞けないプログラマ用語読み方いろいろ
http://dos-sonority.jugem.jp/?eid=1270
最後に
• このスライドはあくまでC++11コンパイル時処理の一端に過ぎない
• C++14ではconstexpr関数の制限が大幅に緩和されより気軽に
constexprと戯れることができるようになる
• その処理はコンパイル時にできないか、できるならコンパイル時と
実行時のどちらにやるのが効率がいいかを見直すのも面白い
• 皆さんのより良いコンパイル時処理ライフを願って
ご清聴ありがとうございました

リテラル文字列型までの道