template meta programming入門から応用まで
TRANSCRIPT
Template Meta Programming入門から応用まで
@fimbul11
2013/09/28 C++勉強会 @ tkb #2
自己紹介
twitter : @fimbul11github : fimbul
University of Tsukuba B3 専攻 : 情報とメディア技術(mast)
プログラミングとかやってます、歴は2年ちょっとぐらいですC++とかやってます、歴は1年半ぐらいですペン回しとか出来ます、歴は7年ぐらいです
このような無名の勉強会に遠方からも多くの人に来ていただけました、本当にありがとうございます
はじめに
おしながき
1. Templateの基本確認2. Template Meta Programmingとは3. Template Meta Programming入門4. Template Meta Programming応用5. その他付録6. まとめ7. 質疑応答
おしながき
1. Templateの基本確認2. Template Meta Programmingとは3. Template Meta Programming入門4. Template Meta Programming応用5. その他付録6. まとめ7. 質疑応答
Templateの基本確認今日は・C++未経験者・C++初心者・C++初心者(意味深)
・C++中級者及び上級者・歩くC++規格レベル・人間C++コンパイラなど色々な方が居る可能性があるので
Templateの基本確認今日は・C++未経験者・C++初心者・C++初心者(意味深)
・C++中級者及び上級者・歩くC++規格レベル・人間C++コンパイラなど色々な方が居る可能性があるので
Templateの基本確認今日は・C++未経験者・C++初心者・C++初心者(意味深)
・C++中級者及び上級者・歩くC++規格レベル・人間C++コンパイラなど色々な方が居る可能性があるので
Templateの基本確認今日は・C++未経験者・C++初心者・C++初心者(意味深)
・C++中級者及び上級者・歩くC++規格レベル・人間C++コンパイラなど色々な方が居る可能性があるので
Templateの基本確認今日は・C++未経験者・C++初心者・C++初心者(意味深)
・C++中級者及び上級者・歩くC++規格レベル・人間C++コンパイラなど色々な方が居る可能性があるので
Templateの基本確認今日は・C++未経験者・C++初心者・C++初心者(意味深)
・C++中級者及び上級者・歩くC++規格レベル・人間C++コンパイラなど色々な方が居る可能性があるので
Templateの基本確認今日は・C++未経験者・C++初心者・C++初心者(意味深)
・C++中級者及び上級者・歩くC++規格レベル・人間C++コンパイラなど色々な方が居る可能性があるので
Templateの基本確認
ガチ勢の方はお手柔らかにお願いします
Templateの基本確認
初心者は知らないことがあるかもそうでない人も少しおさらいをしておく
という意味で
Templateの基本確認
規格に基づいて文法などのTemplateの基本を確認する
Templateの基本確認
規格に基づいて文法などのTemplateの基本を確認する
むしろややこしいTemplateのルールについて把握する事こそがTemplate Meta Programming
最大の山場といえます
Templateの基本確認
規格に基づいて文法などのTemplateの基本を確認する
非常に重要な部分であり、templateを使った経験が少ないとこれだけで十分すぎるぐらいつらいと思います
Templateの基本確認
・C++11規格に沿う・ただし細かい部分には触れない (各自で規格を確認してほしい 自分もまる覚えはしていない)
・C++14~の機能(Variable Templates等)
はその他付録の章で扱う
Templateの基本確認
結構量があるので既に知ってる人は適当に聞き流しつつ他の作業でもしていて下さい
Templateの基本確認目次1. Template is 何2. テンプレートの文法
2-1. テンプレートパラメータ 2-2. クラステンプレート 2-3. 関数テンプレート 2-4. エイリアステンプレート 2-5. 可変長引数テンプレート 2-6. template template parameter
Templateの基本確認目次1. Template is 何2. テンプレートの文法
2-1. テンプレートパラメータ 2-2. クラステンプレート 2-3. 関数テンプレート 2-4. エイリアステンプレート 2-5. 可変長引数テンプレート 2-6. template template parameter
Template is 何
テンプレートって何ですか?
Template is 何
テンプレートって何ですか?N3337見ましょう
Template is 何
14 Templates
A template defines a family of classes or functions or an alias for a family of types.
Template is 何
テンプレートは
クラスや関数、型エイリアスのファミリーを定義する
Template is 何
確認するまでもなかったかもしれません
次に文法を確認していくことにします
Templateの基本確認目次1. Template is 何2. テンプレートの文法
2-1. テンプレートパラメータ 2-2. クラステンプレート 2-3. 関数テンプレート 2-4. エイリアステンプレート 2-5. 可変長引数テンプレート 2-6. template template parameter
テンプレートの文法
テンプレートの文法を確認する
テンプレートの文法
テンプレートの文法を確認する規格を参照して定義から見ていく
テンプレートの文法
テンプレートの文法を確認する
template-declaration:template < template-parameter-list > declaration
template-parameter-list: template-parametertemplate-parameter-list , template-parameter
テンプレートの文法
テンプレートの文法を確認する
template-declaration:template < template-parameter-list > declaration
template-parameter-list: template-parametertemplate-parameter-list , template-parameter
テンプレートの文法
テンプレートは
template < template-parameter-list > declaration として定義される
テンプレートの文法
テンプレートは
template < template-parameter-list > declaration として定義される
テンプレートの文法
template-parameter-listは
template-parameter 又はtemplate-parameter-list , template-parameter
として定義される
テンプレートの文法
template-parameter-listは
template-parameter 又はtemplate-parameter-list , template-parameter
として定義される
Templateの基本確認目次1. Template is 何2. テンプレートの文法
2-1. テンプレートパラメータ 2-2. クラステンプレート 2-3. 関数テンプレート 2-4. エイリアステンプレート 2-5. 可変長引数テンプレート 2-6. template template parameter
Template parametersThe syntax for template-parameters is:
template-parameter: type-parameterparameter-declaration
type-parameter:class ...opt identifieropt
class identifieropt= type-idtypename ...opt identifieropttypename identifieropt= type-idtemplate < template-parameter-list > class ...opt identifieropt template < template-parameter-list > class identifieropt= id-expression
optの後の要素は省略可能
Template parametersThe syntax for template-parameters is:
template-parameter: type-parameterparameter-declaration
type-parameter:class ...opt identifieropt
class identifieropt= type-idtypename ...opt identifieropttypename identifieropt= type-idtemplate < template-parameter-list > class ...opt identifieropt template < template-parameter-list > class identifieropt= id-expression
optの後の要素は省略可能
Template parameters
template-parameterは
type-parameter又はparameter-declaration
である
Template parameters
template-parameterは
type-parameter又はparameter-declaration
である
まずtype-parameterの定義の一部のみ紹介する
type-parameter : class ...opt identifieropt
class identifieropt= type-idtypename ...opt identifieropttypename identifieropt= type-id
optが付いた要素は省略可能一見種類が多い
type-parameter
まずtype-parameterの定義の一部のみ紹介する
type-parameter : class ...opt identifieropt
class identifieropt= type-idtypename ...opt identifieropttypename identifieropt= type-id
対応する色のもの同士は実質同じまた = type-idの部分はデフォルト引数の有無
type-parameter
定義の数は一見多いが実際は結構単純
順番に定義の意味を見ていく
type-parameter
この4つにおいてtype-parameterの頭の部分ではclassキーワードとtypenameキーワードは同じ意味(他の場所では当然意味が違うので注意する、C++には文脈によって同じキーワードに複数の意味を持たせる文化がある)(例えばtemplate template parameterの場合違う)
type-parameter
最も単純なtype-parameterを使ったtemplate例
template<class> (又はtemplate<typename>)
type-parameterのclassとtypenameキーワードは今後の説明ではclassで統一する(スライドの都合上、短い方がありがたい)
type-parameter
type-parameter : class ...opt identifieropt
identifierはtype-parameterの名前
例template<class Type>
type-parameter
type-parameter : class ...opt identifieropt = type-id
type-idがデフォルト引数になる
例template<class Type = int>
何も渡されなければTypeはintになる
type-parameter
関数のデフォルト引数同様に
最初のデフォルト引数を持つパラメータ以降の全パラメータにデフォルト引数が必要
例template<class T1 = int, class T2 = int> class A;
type-parameter
デフォルト引数の設定は複数に分けて書いても統合される(N3337 305p)
template<class T1, class T2 = int> class A;template<class T1 = int, class T2> class A; は以下に等しい
template<class T1 = int, class T2 = int> class A;
type-parameter
当然後ろの要素から順に設定する必要があるtemplate<class T1 = int, class T2> class A;template<class T1, class T2 = int> class A;
この順だと1行目でダメです
error: no default argument for ‘T2’
type-parameter
例まとめtemplate<class> 無名template<class = int> デフォルト引数有りtemplate<class Type> 名前有りtemplate<class Type = int> デフォルト引数有りtemplate<class T1, class T2> 複数のパラメータ
type-parameter
別の定義は後の章で見るのでひとまずtype-parameterをこれで終わります
type-parameter
Template parameters
template-parameterは
type-parameter又はparameter-declaration
である
parameter-declaration
parameter-declaration:attribute-specifier-seqopt decl-specifier-seq declarator
attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause
attribute-specifier-seqopt decl-specifier-seq abstract-declaratoropt
attribute-specifier-seqopt decl-specifier-seq abstract-declaratoropt= initializer-clause
parameter-declarationは
non-type template-parameter (非型テンプレートパラメータ)の場合に使う
parameter-declaration
例を見た方が早いtemplate<char C>template<int N>
渡される引数がnon-typeなtemplate-parameter
int Nやchar Cがparameter-declarationにあたる
non-type template-parameter
もっと具体的な例
template<
class T, // Tはまだ何型か分からない std::size_t N // Nはstd::size_t型のパラメータ> struct array;
std::array<int, 10>の要素数10を受け取っているNがnon-type template-parameter
non-type template-parameter
全ての型が
non-type template-parameterに
なれるわけではない
non-type template-parameter
non-type template-parameterになれる条件(重要)
1. integral or enumeration type(実質整数型)
2. pointer to object or pointer to function3. lvalue reference to object or lvalue reference to function4. pointer to member 5. std::nullptr_t (実質ポインタ型) (又はこれらにcv修飾子が付いたもの)
のいずれかを満たす型であるとき
non-type template-parameter
non-type template-parameterになれる条件は
要するに整数型かポインタ型か左辺値参照
non-type template-parameter
例template<std::size_t N>std::size_tは整数型のtypedefなのでwell-formed
class myClass {};template<myClass Arg>先程の条件を満たさないクラス等はill-formed
non-type template-parameter
例 N3337 p305
template<double d> class X; // Error浮動小数点型は条件に一致しないtemplate<double* pd> class Y; // OKpointer to objectなのでOK
template<double& rd> class Z; // OKlvalue referenceなのでOK
non-type template-parameter
右辺値は値渡しで受け取る必要があるnon-type template-parameterになれないrvalue referenceは論外だし、const lvalue referenceでもダメ(どうせ渡せる型が小さいデータなのでコピーで然程問題ない)
例template<int N> class X;using type = X<0>; // OK
template<const int& N> class Y;using type = Y<0>// error: ‘0’ is not a valid template argument because it is not an lvalue
non-type template-parameter
non-type template-parameterに渡せるものにも条件がある
non-type template-parameter
non-type template-parameterに渡せるもの
・定数式・関数のアドレス・外部リンケージのあるオブジェクト・静的クラス・メンバのアドレス
non-type template-parameter
non-type template-parameterに渡せるもの
定数式(分類に不安があるが…)
・非型テンプレート引数(先程のNなど)
・コンパイル時定数(constexpr)
(後で説明する)
・sizeofやsizeof...など
non-type template-parameter
non-type template-parameterに渡せるもの
関数のアドレス・説明不要、f()などのアドレス
non-type template-parameter
non-type template-parameterに渡せるもの
外部リンケージのあるオブジェクト・extern修飾されたもの
non-type template-parameter
non-type template-parameterに渡せるもの
静的クラス・メンバのアドレス・説明不要、そのままの意味
non-type template-parameter
template-parameterに関しては、他にも細かい事が大量に規定されているので適宜規格を調べる必要がある
template-parameter
template-parameterまとめ・テンプレートパラメータには型パラメータ と非型パラメータがある・非型パラメータになるものや渡せるものは 限られている
template-parameter
Templateの基本確認目次1. Template is 何2. テンプレートの文法
2-1. テンプレートパラメータ 2-2. クラステンプレート 2-3. 関数テンプレート 2-4. エイリアステンプレート 2-5. 可変長引数テンプレート 2-6. template template parameter
Class templates
A class template defines the layout and operations for an unbounded set of related types.
Class templates
クラステンプレートは、関連する型(要するに扱う要素の型が違うだけのクラス群)のレイアウトをまとめて定義する
Class templates例 汎用的な配列クラス N3337 P315template<class T> class Array {
T* v; // Tは引数として渡された型に置き換わるint sz; public: explicit Array(int); T& operator[](int); T& elem(int i) { return v[i]; }
};
Array<int>やArray<char>は渡した型を扱える
Class templatesクラスメンバテンプレート
class Test {public:
template<class T>T f(T arg) { return arg; } // 関数テンプレートは後で取り上げる
};
クラスメンバもテンプレートに出来るoperator等もテンプレート化可能
Class templates非型テンプレートパラメータを使う例template<std::size_t N>struct integer { // structも当然テンプレート化可能
static constexpr std::size_t value = N;}; Nの型にconst等は付いていなくても非型テンプレートパラメータは(暗黙に)コンパイル時に値が決まる定数として扱える(constexpr修飾された値の初期化にも使える)
Class templates非型テンプレートパラメータを使う例template<std::size_t N>struct integer {
static constexpr std::size_t value = ++N;}; 当然、非型テンプレートパラメータの型の書き換えはill-formedである(定数なのでうっかり試みても失敗する)
error: increment of read-only location ‘N’
Class templates
違法な例を招く行為(当然してはいけない)template<class T, T v>struct data { static constexpr T* get_address() {
return const_cast<T*>(&static_cast<const T&>(v));}
};
このような行為は万死に値する
Class templates
補足事項Template specializations テンプレートの特殊化
Class templates
Template specializationsテンプレートの特殊化template<class T = int> struct A { static int x;};template<class U> void g(U) { }template<> struct A<double> {}; // specialize for T == doubletemplate<> struct A<> { }; // specialize for T == inttemplate<> void g(char) { } // specialize for U == char
テンプレートクラスやテンプレート関数のtype-parameterにある値が入った状態のものは特殊化されているといえる
Class templates
class template partial specializationsクラステンプレートの部分特殊化
Class templates
例template<class T1, class T2, int I> class A {}; // unspecialized
template<class T, int I> class A<T, T*, I> {};template<class T1, class T2, int I> class A<T1*, T2, I> {};template<class T> class A<int, T*, 5> {};template<class T1, class T2, int I> class A<T1, T2*, I> {};
Class templates
template<class T1, class T2, int I> class A {}; // unspecialized
template<class T, int I> class A<T, T*, I> {};
このように抽象的なテンプレートパラメータの数を減らし部分的に具体的要素に置き換えた特殊化を作る事が出来る
Class templates
template<class T1, class T2, int I> class A {}; // unspecialized
template<class T, int I> class A<T, T*, I> {};
部分特殊化されたテンプレートは、条件を満たす場合には特殊化されていないテンプレートより優先的にマッチする
Class templates
template<class T1, class T2, int I> class A {}; // unspecialized
template<class T, int I> class A<T, T*, I> {}; // classA<int, int*, 0>などの場合だけこちらの定義が使われる
ある条件を満たす場合のみ実装を切り替える事が出来るこれはTemplate Meta Programmingにおいて非常に重要
Class templates
また、部分特殊化によってはtemplate<int N>struct A;
template<>struct A<0>;のようにtemplateの中身が空<>になることもあるが特に文法上問題無い
Class templates
勿論、型テンプレートパラメータだけでなく非型テンプレートパラメータも部分特殊化出来る(例は後の章で登場する)
Class templates
テンプレートの特殊化及び部分特殊化が出来るケースにも条件がある
Class templates
テンプレートの特殊化が出来るケース特殊化できるOK : 名前空間スコープNG : クラススコープ
つまりクラスのメンバは特殊化不可能
Class templates
テンプレートの部分特殊化が出来るケース部分特殊化できる:
OK : クラステンプレートNG : 関数テンプレート
部分特殊化は非クラスメンバのクラステンプレート限定の機能といえる
Class templates
Class templatesまとめ・クラステンプレートは、 関連する型のレ イアウトをまとめて定義する・名前空間スコープのクラステンプレートの み部分特殊化によって実装を切り替え可
Templateの基本確認目次1. Template is 何2. テンプレートの文法
2-1. テンプレートパラメータ 2-2. クラステンプレート 2-3. 関数テンプレート 2-4. エイリアステンプレート 2-5. 可変長引数テンプレート 2-6. template template parameter
Function templates
A function template defines an unbounded set of related functions.
Function templates
関数テンプレートは、関連する関数(要するに扱う要素の型が違うだけの関数群)をまとめて定義する
Function templates
例template<class T>T twice(T arg) {
return arg * 2;}
twice(2); // 4twice(2.5); // 5.0
整数も浮動小数点も扱える
Function templates
Template argument deductionテンプレート引数の型推論
Function templates
先程の例twice(2); twice(2.5);
twice<int>(2)と書かなくとも適切にint版のtwiceが呼ばれ、twice<double>(2.5)と書かなくとも適切にdouble版のtwiceが呼ばれた
Function templates
このような引数からテンプレートパラメータ型を推論する機能が
Template argument deduction
Function templates
幾つかの例外を除き、渡した型がそのまま推論される
Function templates
例外1
・配列型(T[N])はポインタ(T*)に変換される・関数型(R(Args...))は関数ポインタ (R(*(Args...))に変換される
Function templates
例外2
・値渡しの場合のみトップレベルのcv修飾子 が無視される
Function templates
例外2
template<class T>T test(T arg) { return arg; }
int a = 0;test(a); // Tはint型
const int b = 0;test(b); // Tはint型
Function templates
例外2
このような挙動の理由は、値コピーを取る場合、結局元のオブジェクトの値が変更される可能性が無いためとされる
const int b = 0;test(b); // (testの中ではbのコピーが扱われるのでbの値自体が変えられることはない、じゃあコピーするときはconst無視してもいいんじゃね)…とコンパイラは思う
Function templates
例外2
constと同様にvolatileも無視される
Function templates
例外2
よって値を変更する可能性のある参照やポインタ渡しの場合はcv修飾子も保持される
template<class T>T test(T& arg) { return arg; }
const int b = 0;test(b); // Tはint& const型
Function templates
例外2
ただし明示的に渡せば、渡した通りになる
const int b = 0;test<const int>(b); // Tはconst intになる
Function templates
例外2
cv修飾子が無視される可能性があるのはトップレベルの型のみである
例えばvector<const int>などのconst int部分はトップレベルではないので無視されない
Function templates
当然非型テンプレートも推論してくれるtemplate<class T, T N>constexpr T get(std::integral_constant<T, N>) {
return N;}std::cout << get(std::integral_constant<int, 0>()) << std::endl; // T = int, N = 0と推論され、0が出力される
Function templates
std::integral_constant<T, N>
のようにテンプレートクラスのテンプレートパラメータとしてネストされた内部の型でもTやNとして推論出来る
Function templates
Template argument deductionの規格を厳密に読んで全てのルールを把握していくだけで尋常じゃない大変さなので残りは省略します
Function templates
Function template overloading関数テンプレートのオーバーロード
Function templates
部分特殊化は出来ないが特殊化は出来るこれらはオーバーロードされる
template<class T> void output(T val);
template<> void output(const std::string& val);
Function templates
オーバーロードの解決に関しては省略する
N333714.5.6.2 Partial ordering of function templates を参照して欲しい
ambiguousになる例など取り上げられている
Function templates
Function templatesまとめ
・関数テンプレートは引数から型を推論し てくれる・特殊化、オーバーロードが可能である
Templateの基本確認目次1. Template is 何2. テンプレートの文法
2-1. テンプレートパラメータ 2-2. クラステンプレート 2-3. 関数テンプレート 2-4. エイリアステンプレート 2-5. 可変長引数テンプレート 2-6. template template parameter
Alias templates
1. A template-declaration in which the declaration is an alias-declaration declares the identifier to be a alias template. An alias template is a name for a family of types. The name of the alias template is a template-name.
Alias templates
2. When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template.
Alias templates
もはや訳すのも面倒なので実例を見る
Alias templates
template <class T>using add_pointer = typename std::add_pointer< T >::type;
add_pointer<T>はstd::add_pointer< T >::typeと同じ型になる
Alias templates
別にtemplateである必要は無い
using type = int;
typeはint型の別名として使える、簡単!
Alias templates
typedef int integer;
using integer = int;
は同じ効果typedefの代わりとして使える(ただし使えるのはC++11以降)
Alias templates
Template Meta Programmingではtypenameとか::typeを書かなくて済むようにする為に使ったりする
template<class T>using add_p = typename std::add_pointer<T>::type;
add_p<int>; // int*
Alias templates余談typedefは指定子である指定子の順番に意味は無い
typedef int Integer1; (こっちが多分メジャー)
int typedef Integer2; (稀に見かける)
// どちらもOK
int Integer3 typedef; // これはダメ
Alias templates
typedef int Integer1; のようにどうしてtypedef A B;の形がメジャーなのか?
Alias templates
次のような例を考えるint typedef type; はOKだがint* typedef type; はダメ
「*」は、指定子(specifier)ではなく、宣言子(declarator)のため
Alias templates
結論多分、typedefを最初に書かない場合には宣言子が出てきた時に面倒臭いから
Alias templates
後方互換性を気にしなくていいなら正直typedefよりusingを使うと良いと思います(個人的にはusingの方が好き)
Alias templates
余談template structなどの状態のメタ関数では遅延されるタイミングで正格評価される場合がある?(エイリアステンプレートにしたところコンパイル時間が大幅に伸びた経験がある)
Alias templates
Alias templatesまとめ・usingを使う事で型の別名として使える・typedefと違いテンプレート化出来る
Templateの基本確認目次1. Template is 何2. テンプレートの文法
2-1. テンプレートパラメータ 2-2. クラステンプレート 2-3. 関数テンプレート 2-4. エイリアステンプレート 2-5. 可変長引数テンプレート 2-6. template template parameter
Variadic templates
1. A template parameter pack is a template parameter that accepts zero or more template arguments.
Variadic templates
2. A function parameter pack is a function parameter that accepts zero or more function arguments.
Variadic templates
つまり、
・可変長引数を取るクラス・可変長引数を取る関数
の為の可変長引数テンプレート
Variadic templatesThe syntax for template-parameters is:
template-parameter: type-parameterparameter-declaration
type-parameter:class ...opt identifieropt
class identifieropt= type-idtypename ...opt identifieropttypename identifieropt= type-idtemplate < template-parameter-list > class ...opt identifieropt template < template-parameter-list > class identifieropt= id-expression
optの後の要素は省略可能
Variadic templates
先程Template parametersで飛ばした...opt を使う
また、「...」を規格上ellipsisと呼ぶ先の定義通りデフォルト引数は取れない
Variadic templates
template<class... Types> struct Tuple {};
class...の部分は可変長引数テンプレートその名前はTypes
Variadic templates
template<class... Types> struct Tuple {};
可変長引数テンプレートは0個以上の引数を好きなだけ受け取れる
勿論、非型テンプレート引数版も可能template<int... Indices> struct Numbers {};
Variadic templates
template<class... Types> struct Tuple {};
例Tuple<int, double, char, void*> このように好きなだけ渡せる
template<int... Indices> struct Numbers {};Numbers<1,2,3,4,5>
Variadic templates
このような可変長引数部分の事をtemplate parameter pack と呼ぶ
Variadic templates
例template<class... Types>struct List;
template<class Type, Type... Args>struct Data;
TypesやArgsはTemplate Parameter Pack
Variadic templates
可変長引数テンプレートは基本的には、template parameterのうち最後でなければならない(一部例外がある)
template<class Type, class... Types> // OKtemplate<class... Types, class Type> // NG
Variadic templates例外template<std::size_t... Indices1, std::size_t... Indices2>void f(index_tuple<Indices1...>, index_tuple<Indices2...>);
・関数の引数の型が持つパラメータパック を推論する場合・特に可変長引数を持つ型の引数を複数受 け取る場合には複数のパラメータパック を持つことが出来る
Variadic templates
pack expansion 受け取ったパラメータパックを展開する
Variadic templates
可変長引数として受け取ったテンプレートパラメータパックを使う為には
それを展開する必要がある
Variadic templates
展開はpattern and an ellipsisからなる
Variadic templates
template<class ResultType, class... Types>ResultType sum(const Types&... Args);
sum<int>(1,2,3,4,5,6,7,8,9,10);
可変長の関数テンプレートにおいて型推論を行い、その結果をTypesに格納して受け取っている
Variadic templates
template<class Type, class... Types>Type sum(const Types&... Args);
におけるconst Types&...がパック展開
class... Typesはsum<int>(1,2,3,4,5,6,7,8,9,10);
なのでTypesはint, int, int, int, int, int, int, int, int, int
に推論される
Variadic templates
このときTypes...はint, int, int, int, int, int, int, int, int, int に展開される
Variadic templatespatternはパック展開時に処理を施すconst Types&...はTypesの受け取った型1つ1つにconst修飾及び参照&を付加した型になる
よってconst Types&...はconst int&, const int&, const int&, const int&, const int&, const int&, const int&, const int&, const int&, const int& に展開される
Variadic templates結果としてtemplate<class ResultType, class... Types>ResultType sum(const Types&... Args);はsum<int>(1,2,3,4,5,6,7,8,9,10);のとき
int sum<int>(const int&, const int&, const int&, const int&, const int&, const int&, const int&, const int&, const int&, const int&);
と等価になっている
Variadic templates
同様にして関数の引数もparameter packとして受け取っている
template<class ResultType, class... Types>ResultType sum(const Types&... Args);
Variadic templates
sum<int>(1,2,3,4,5,6,7,8,9,10);のとき
Args...は1,2,3,4,5,6,7,8,9,10になる
Variadic templates例として受け取った数の総和を計算する関数を挙げるtemplate<class Type, class... Types>Type sum(const Types&... Args) { // parameter pack Type result = 0; for(auto& elem : { static_cast<Type>(Args)... }) // pack expansion { result += elem; } return result;}
int main() { std::cout << sum<int>(1,2,3,4,5,6,7,8,9,10) << std::endl; // 55}
Variadic templatesこの例ではinitializer-listに対して
{ static_cast<Type>(Args)... }
を展開することで
{static_cast<Type>(1), static_cast<Type>(2), static_cast<Type>(3),
中略, static_cast<Type>(10)}
を生成している
結果として今回は全要素がint型の{1,2,3,4,5,6,7,8,9,10}になる
Variadic templates
sum<int>(1,2.0,3.5,4,5,6,7,8,9,10);
のように型が異なる要素が混入していてもキャストしながら展開する事が出来る
Variadic templates
補足template parameter packの要素数を得るにはsizeof...を使う
sizeof...(Types)sizeof...(Args)
sizeof...に対してはパックは展開せずに渡すsizeof...(Types...) // NG
Variadic templates
余談ピリオドが6つ連続するコードが存在し得る(多分Sprout等で稀にお目にかかれる)
例template<class... T>void f(T......); // なにこれキモイ
Variadic templates
これは前半3つのピリオドがVariadic Templatesの展開、後半3つのピリオドがC言語スタイルの可変長引数の...であり、真ん中のカンマが省略されたもの
template<class... T>void f(T..., ...); // ただのellipsis, ellipsisにすぎない
に等しいのでビビる必要は無い
Variadic templates
Variadic templatesまとめ・Variadic TemplatesはTemplate Parameter Packと して受け取る・型パラメータ、非型パラメータを扱える・型パラメータは関数の引数の型として利用出来る・非型パラメータは関数の引数として利用出来る・Template Parameter Packはpattern and ellipsis
によって展開してから使う
Templateの基本確認目次1. Template is 何2. テンプレートの文法
2-1. テンプレートパラメータ 2-2. クラステンプレート 2-3. 関数テンプレート 2-4. エイリアステンプレート 2-5. 可変長引数テンプレート 2-6. template template parameter
The syntax for template-parameters is:
template-parameter: type-parameterparameter-declaration
type-parameter:class ...opt identifieropt
class identifieropt= type-idtypename ...opt identifieropttypename identifieropt= type-idtemplate < template-parameter-list > class ...opt identifieropt template < template-parameter-list > class identifieropt= id-expression
optの後の要素は省略可能
Template Template Parameters
Template Template Parameters
template <class Container>class hoge; ではhoge<std::vector<int>> h; は出来てもhoge<std::vector> h; は出来ない
Template Template Parameters
要するにテンプレートクラス自体などを受け取る為のテンプレートパラメータ
Template Template Parameters
アキラさんのブログに分かりやすい例があったので引用するtemplate <class T>class vector;
template <template<class> class Container>class hoge { Container<int> c;};
hoge<vector> h; // OK
Template Template Parameters
template < template-parameter-list > class ...opt identifieropt template < template-parameter-list > class identifieropt= id-expression
このようにして受け取れるoptにellipsisがあるように可変長引数でも可能
Template Template Parameters
Template Template Parametersまとめ・テンプレートクラス等を受け取る場合に 使う為のTemplate Parameter
おしながき
1. Templateの基本確認2. Template Meta Programmingとは3. Template Meta Programming入門4. Template Meta Programming応用5. その他付録6. まとめ7. 質疑応答
Template Meta Programmingとは
お疲れ様でした、テンプレートの機能をひと通り確認するだけで絶望的な量でした
これでも相当、本当に沢山端折って説明したので規格自体はこの数倍の量があります
Template Meta Programmingとは
がっかりしたところで、初心者も最低限の知識が付いたと思うので二章に入ります
Template Meta Programmingとは
Template Meta Programmingとは何か
Template Meta Programmingとは
テンプレートを利用して、型や値に関する演算・処理をコンパイル時に行う技法
よく省略してTMPと呼ばれる
Template Meta Programmingとは
テンプレートはコンパイル時に処理されるのでこの技法ではコンパイル時に計算が出来る
Template Meta Programmingとは
この技法を用いた機能が標準ライブラリにもtype_traitsをはじめ多く含まれている
Template Meta Programmingとは
大きく分けて
・型を処理するもの・値(として型)を処理するもの
に分けられる
Template Meta Programmingとは
型を処理するものはたいてい
template<class T>
のような型パラメータを使って型を操作していく
Template Meta Programmingとは
値(として型)を処理するものはたいてい
template<int N>
のような非型パラメータを使って値(として使えるような型)を操作していく
Template Meta Programmingとは
次章で実例を見ていくとしましょう
おしながき
1. Templateの基本確認2. Template Meta Programmingとは3. Template Meta Programming入門4. Template Meta Programming応用5. その他付録6. まとめ7. 質疑応答
Template Meta Programming入門
この章では実例を見ていく初心者向けに最も単純な例から取り上げる
Template Meta Programming入門
例1
型を受け取って値を返すもの
Template Meta Programming入門
例えばある2つの型を比較して
同じ型であればtrue
異なる型であればfalse
を得たい
Template Meta Programming入門
そのような機能を提供する
std::is_same
が標準ライブラリにはある
Template Meta Programming入門
これは以下のように使う
std::is_same<int, int>::value // truestd::is_same<int, unsigned int>::value // false
Template Meta Programming入門
実装はどうなっているのか
Template Meta Programming入門
宣言template<class T, class U>struct is_same; ただ2つの型を受け取る
Template Meta Programming入門
問題はどうやって判別して結果を変えるか
Template Meta Programming入門
答え : 部分特殊化を使う
Template Meta Programming入門
template<class T, class U> // different typesstruct is_same {
static constexpr auto value = false;};
template<class T> // same typestruct is_same<T, T> {
static constexpr auto value = true;};
Template Meta Programming入門
template<class T>struct is_same<T, T> {
static constexpr auto value = true;};
両辺に同じ型が渡された場合のみこちらの定義が使われる
Template Meta Programming入門
条件分岐 → 部分特殊化を使う
Template Meta Programming入門
結果の返し方
静的なメンバ定数を定義するstatic constexpr auto value = true; // ただの静的定数でも良い
そうすれば、is_same<int, int>::valueのようにして得られる
Template Meta Programming入門
このような実質関数として働くようなテンプレートクラスはmeta functionと呼ばれる
Template Meta Programming入門
例2
値を受け取って型を返すもの
Template Meta Programming入門
条件式によって違う型を返すメタ関数
Template Meta Programming入門std::conditionalと同じ働きのメタ関数
template<bool B, class T, class F> // truestruct conditional { using type = T; }; template<class T, class F> // falsestruct conditional<false, T, F> { using type = F; };
型を返す場合はメンバ型を定義してやる
typename conditional<true, int, char>::type // int
Template Meta Programming入門
例3
非型テンプレートパラメータによる演算
Template Meta Programming入門
template<int X, int Y>struct add {
static constexpr auto value = X + Y;};
add<1, 2>::value // 3
非型パラメータを用いてコンパイル時に演算
Template Meta Programming入門
発展例階乗の計算
Template Meta Programming入門template<int i>struct fact{ static constexpr auto value = i * fact<i - 1>::value;}; template<>struct fact<1> // 1の場合{ static constexpr auto value = 1;};
階乗の計算ではvalueの定義にfact<i - 1>::valueを利用することで再帰を実現する
Template Meta Programming入門
template<>struct fact<1> // 1の場合{ static constexpr auto value = 1;};
1の部分特殊化が無いと再帰が止まらなくなる事に注意する
Template Meta Programming入門注意template<int i>struct fact{ static constexpr auto value = i > 1? i * fact<i - 1>::value : 1; // 再帰が止まらない};
三項演算子はtrueの場合も両辺の型の等価性を確認するために両辺ともインスタンス化しようとするので、再帰が止まらない(trueでもfact<i - 1>::valueをインスタンス化する)
Template Meta Programming入門余談インスタンス化を遅らせて出来る限り型のまま処理することで部分特殊化無しで実装出来る場合もあるtemplate<std::size_t i>struct fact{ static constexpr auto value = i * std::conditional< (i > 1), fact<i - 1>, std::integral_constant<std::size_t, 1> >::type::value;}; これは再帰が止まるconditionalの両辺をメタ関数にして遅延評価させる
Template Meta Programming入門まとめ1
メタ関数には単純な例だけでも入力として・値を受け取るもの・型を受け取るもの出力として・値を返すもの・型を返すものがある
Template Meta Programming入門まとめ2
条件分岐・クラステンプレートの部分特殊化を使う(後でenable_if等も取り上げる)
結果の返し方・型を返すならばメンバ型を定義する・値を返すならば静的メンバ定数を定義する
名前は慣習として型であればtype、値であればvalueを使う
Template Meta Programming入門
補足コンパイル時にエラーチェックさせる
Template Meta Programming入門配列の要素数は正の値であるべきだtemplate<class T, std::size_t N, bool = (N > 0)> struct array; // 実装無し template<class T, std::size_t N>struct array<T, N, true> {// 実装}; array<int , 0> a; // 実装が無いのでコンパイルエラーになる
このような不正値に対して意図的にコンパイルエラーに出来るようなコードを書く技法は良く使われる
Template Meta Programming入門
template<class T, std::size_t N, bool = (N > 0)>
比較演算子は括弧で囲む必要がある
std::vector<std::vector<int>>のようなネストされたテンプレートが>>演算子に解釈される問題はC++11以降では平気
Template Meta Programming入門
補足static_assert
Template Meta Programming入門コンパイル時アサートstatic_assert(fact<6>::value == 720, "");
コンパイル時に計算可能な値に対して用いるtrueであれば無出力falseであればコンパイルエラーになる
1番目の引数に条件式2番目の引数にエラー時のメッセージを渡す
当然、実行時計算されるような値を渡すと失敗扱い
おしながき
1. Templateの基本確認2. Template Meta Programmingとは3. Template Meta Programming入門4. Template Meta Programming応用5. その他付録6. まとめ7. 質疑応答
1. 関連知識をおさえる2. 実践(有用なイディオムと例)
Template Meta Programming応用
Template Meta Programming応用
これからTMPの応用に入るその前に関連知識を知っておく
Template Meta Programming応用
・constexpr
・decltype
・SFINAE
補足constexpr
Template Meta Programming応用
C++11のconstexprに関して軽く説明する私は陶芸家ではないので今回は深入りはしない
constexprに関して深く知りたい場合は・中3女子でもわかる!constexpr
・中3女子が教える本当に気持ちのいい constexpr
・constexpr中3女子テクニックなどの有益な情報の載ったスライドがWeb上に公開されているので各自Web上で検索して読むなどすると良い
Template Meta Programming応用
TMPにおける値の計算は整数しか扱えないという弱点がある(浮動小数点を無理矢理エミュレートしようとする人は居るが…)
(また、一応ratioのようなものもあるが…)
またコンパイル時にしか使えないint a = 0;add<a, 1>::value; // エラー、aは実行時オブジェクト
Template Meta Programming応用
C++03やconstexpr非対応のコンパイラの場合はTMPを使うが、そうでない場合はただ値を計算して、値を返すだけのようなメタ関数はconstexpr関数で実装した方が好ましい事も多いと思われる
Template Meta Programming応用
// コンパイル時に計算可能template<class T>constexpr T add(T x, T y){
return x + y;}
見慣れた関数の形で実装出来る実行時にも共通のインタフェースで使える浮動小数点もOK
Template Meta Programming応用
次のような式はconstant expressionsと呼ばれる。(Constant
expressionはコンパイル時に評価され得る)
constant-expression:conditional-expression
以降の条件部分はかなり細かいので、発表では適当に流すので各自確認しておいて欲しい
次ページ以降の条件に当てはまる場合、constexprにはなれない
Template Meta Programming応用
条件
・this (非静的メンバ関数の本体に暗黙の変換の結果を含む、クラスメンバアクセス式の後置式、として表れていない場合)
・リテラルクラスのconstexprコンストラクタとconstexpr関数以外の関数呼び出し(オーバーロードの解決は普段通り適用される)
・constexpr関数やconstexprコンストラクタの定義の外での未定義動作のconstexpr関数や、未定義動作のconstexprコンストラクタの呼び出し
Template Meta Programming応用
条件・constant expressionを生成しないような引数を用いてのconstexpr関数の呼び出し
例constexpr const int* addr(const int& ir) { return &ir; } // OK static const int x = 5;constexpr const int* xp = addr(x); // OK constant expressionなアドレスconstexpr const int* tp = addr(5); // エラー 一時アドレスの取得はconstant
expressionではない
Template Meta Programming応用
条件・constant expressionを生成しないような引数による、初期化リストのみからなるconstexprコンストラクタの呼び出し
例int x; // not constantstruct A { constexpr A(bool b) : m(b?42:x) { } int m;};constexpr int v = A(true).m; // OKconstexpr int w = A(false).m; // エラー mの初期化に用いられるxが定数でない
Template Meta Programming応用
条件・constexpr関数又はconstexprコンストラクタの再帰呼び出しが処理系の定義する制限を超える場合(Annex Bを参照)
Annex BにおいてRecursive constexpr function invocations [512].と定義されている・数学上定義されないような値、又は特定の型で扱える範囲外のその型の値。・ラムダ式・次の場合を除く左辺値から右辺値への変換 ・constant expressionで事前に初期化された非volatileなconstオブジェクトを参照する整数型のglvalue又はenum
・constexpr修飾で定義された、非volatileのオブジェクトかそのようなサブオブジェクトを参照するリテラル型のglvalue
・constant expressionで初期化された寿命の尽きていない一時オブジェクトを参照するリテラル型のglvalue
Template Meta Programming応用
条件・非activeなunionのメンバやサブオブジェクトを参照するglvalueに適用される左辺値から右辺値への変換・constant expressionによって初期化されていない状態の値やデータメンバ参照を参照するid-expression
・dynamic cast
・reinterpret_cast
・擬似destructorの呼び出し・インクリメント及びデクリメント操作・多態クラス型に対するtypeid式・new式・delete式
Template Meta Programming応用
条件・ポインタ同士の減算
・結果が不特定な場所での比較に関連するような演算子(rational or equality
operator)
・代入及び複合代入・throw式
Template Meta Programming応用
更に補足実行時計算とコンパイル時計算の浮動小数点演算の精度は異なる場合がある
Template Meta Programming応用
規格では浮動小数点の演算精度に関して制限を課さないので、コンパイル時と実行時における同じ式の浮動小数点演算の結果が同じである保証は無い(N3337 5.19 Constant expressions)
bool f() { char array[1 + int(1 + 0.2 - 0.1 - 0.1)]; // 必ずコンパイル時に計算される int size = 1 + int(1 + 0.2 - 0.1 - 0.1); // 実行時に計算されるかもしれない return sizeof(array) == size;}
f()の結果がtrueであるかfalseであるかは未定義である。
Template Meta Programming応用
・整数演算しか出来ないTMPに比べ constexprは浮動小数点演算もコンパイル 時に出来る・コンパイル時計算にしか使えないTMPに比 べconstexprは実行時計算にも使える・逆に型に関する処理はTMPでしか実現出 来ない(TMPの独壇場)
Template Meta Programming応用
補足decltype
Template Meta Programming応用
decltypeイメージとしてはsizeofの型バージョン
Template Meta Programming応用
decltypeThe type denoted by decltype(e) is defined as follows:— if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;— otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;— otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;— otherwise, decltype(e) is the type of e.
Template Meta Programming応用
decltype
if e is an unparenthesized id-expression or an unparenthesized class member access ...
要するにexpressionが括弧で囲まれていないような
decltype(expression)の形のものの場合
Template Meta Programming応用
例char a;decltype(a); // char
int b = 1;int& c = b;decltype(c); // int&
const int* d;decltype(d); // const int*
Template Meta Programming応用
例struct a_type { using type = int; };
a_type a;decltype(a); // a_typedecltype(a)::type; // int
void f(int);decltype(f); // void (int)
Template Meta Programming応用
例void f(int);void f(double);decltype(f); // Error
関数に複数のオーバーロードがあるとダメ
Template Meta Programming応用
例int f();decltype(f()); // int
関数の戻り値の型も取れる
decltype(e)のeのexpressionが評価されることはないので関数の定義は必要ない(TMPにおいて重要)
Template Meta Programming応用
decltypedecltype((expression))の形のものの場合
Template Meta Programming応用
decltype
otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
decltype((expression))のような括弧で囲まれたもので、かつxvalueの場合T&&になる
xvalueはrvalue referenceを返す関数の呼び出しとそれに準ずるもののイメージ(std::moveなども含まれる)
Template Meta Programming応用
decltype
otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
decltype((expression))のような括弧で囲まれたもので、かつlvalueの場合T&になる
Template Meta Programming応用
decltype
otherwise, decltype(e) is the type of e.
それ以外(prvalue等)はeの型
例decltype(1); // intdecltype("hello,world"); // const char[12]
Template Meta Programming応用
decltypeまとめ・decltypeを使うとオブジェクトや関数の戻 り値の型を得る事が出来る・decltypeの結果は括弧の有無で変わる
Template Meta Programming応用
補足SFINAE
Template Meta Programming応用
SFINAESubstitution failure is not an error
Template Meta Programming応用
テンプレートの置き換えに失敗してもすぐにはエラーにならず他の候補を探す
必修知識だがC++入門書には多分載ってない(結果として知らない人も居る印象)
Template Meta Programming応用
言葉より実例を見るほうが早い
Template Meta Programming応用
struct Test { typedef int foo;}; template <typename T> void f(typename T::foo) {} // Definition #1 template <typename T> void f(T) {} // Definition #2 int main() { f<Test>(10); f<int>(10); }
Template Meta Programming応用
struct Test { typedef int foo;}; template <typename T> void f(typename T::foo) {} // Definition #1 template <typename T> void f(T) {} // Definition #2 int main() { f<Test>(10); f<int>(10); }
Template Meta Programming応用
struct Test { typedef int foo;}; template <typename T> void f(typename T::foo) {} // Definition #1 template <typename T> void f(T) {} // Definition #2 int main() { f<Test>(10); f<int>(10); }
Template Meta Programming応用
struct Test { typedef int foo;}; template <typename T> void f(typename T::foo) {} // Definition #1 定義にマッチする template <typename T> void f(T) {} // Definition #2 int main() { f<Test>(10); // Call #1. f<int>(10); }
Template Meta Programming応用
struct Test { typedef int foo;}; template <typename T> void f(typename T::foo) {} // Definition #1 int::fooは存在しない template <typename T> void f(T) {} // Definition #2 int main() { f<Test>(10); // Call #1. f<int>(10); }
Template Meta Programming応用
struct Test { typedef int foo;}; template <typename T> void f(typename T::foo) {} // Definition #1 呼出候補から外れる template <typename T> void f(T) {} // Definition #2 int main() { f<Test>(10); // Call #1. f<int>(10); }
Template Meta Programming応用
struct Test { typedef int foo;}; template <typename T> void f(typename T::foo) {} // Definition #1 エラーにはならない template <typename T> void f(T) {} // Definition #2 定義にマッチする int main() { f<Test>(10); // Call #1. f<int>(10); // Call #2. }
Template Meta Programming応用
このようなルールをSFINAEと呼ぶ条件分岐、エラーチェック等を実現出来る(例は後述)
Template Meta Programming応用
1. 関連知識をおさえる2. 実践(有用なイディオムと例)
Template Meta Programming応用
Template Meta Programming応用
関連知識をおさえたところでいよいよ応用として実用的なイディオム等を見ていく
Template Meta Programming応用・Variadic templates 可変長引数テンプレートを扱う・enable_if SFINAEによる条件分岐・index_tuple, index_range (index_sequence) 整数列パラメータパックの生成と利用・Expression Template(ET)
式テンプレート
Variadic templates 可変長引数テンプレートを扱う
Template Meta Programming応用
要素の追加型リストを保持する単純なクラスtemplate<class... Types>struct list{
template<class Type>struct push_back{
using type = list<Types..., Type>; // これでOK
};};
Template Meta Programming応用
要素の追加
例えばlist<int, char>::push_back<double>::typeのとき
Types...はint, charに展開され、Typeはdoubleであるから
list<Types..., Type>はlist<int, char, double>
Template Meta Programming応用
要素の追加template<class... Types>struct list{
template<class Type>struct push_front{
using type = list<Type,Types...>; // これでOK
};};
push_frontも同様に可能
Template Meta Programming応用
要素の削除template<class... Types>struct list{private:
template<class Type, class... Types2>struct pop_front_impl{
using type = list<Types2...>;};
public:struct pop_front { using type = typename pop_front_impl<Types...>::type; };
};
Template Meta Programming応用
要素の削除
template<class Type, class... Types2>struct pop_front_impl{
using type = list<Types2...>;};
複数受け取ったパラメータのうちの最初の1つだけTypeに入り、残りは全てTypes2に入る
Template Meta Programming応用
要素の削除pop_backだけは簡単にはいかない実装も他の操作に比べ面倒、計算コストもかかる
template<class... Types2, class Type> // エラー、可変長引数テンプレートはテンプレートパラメータのうち最後でなければならない
再帰的な実装など工夫が必要
Template Meta Programming応用
enable_if SFINAEによる条件分岐
Template Meta Programming応用
enable_if
・SFINAEを利用した条件分岐を手助けする イディオム・標準ライブラリにstd::enable_ifがある
Template Meta Programming応用
enable_if
template<bool B, class T = void>struct enable_if {}; template<class T>struct enable_if<true, T> { typedef T type; };
つまり条件を満たした場合のみstd::enable_if<条件>::type
が定義される(SFINAEに利用出来る)
Template Meta Programming応用
例template<class T>T foo2( T t, typename std::enable_if<std::is_integral<T>::value>::type* = 0) { return t;}
Tが整数型だった場合のみ呼び出し可能な関数Tが非整数型なら呼出候補から外される(もし呼出候補が1つも見つからなければコンパイルエラー)
不正な型の引数が渡される事を防止出来る
Template Meta Programming応用
例template<class T, class Enable = void>class A; // undefined template<class T>class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {};
条件を満たした場合のみ構築可能なクラスを部分特殊化で実現するSFINAEの対象は関数に限られたものではない
Template Meta Programming応用
もっとenable_if enabler idiom
Template Meta Programming応用
従来のenable_ifではオーバーロード出来ないtemplate<typename T, typename Enable = typename std::enable_if<std::is_integral<T>::value>::type>void f(T);
template<typename T, typename Enable = typename std::enable_if<std::is_pointer<T>::value>::type>void f(T); // デフォルト引数が異なるだけの再定義はダメ
Template Meta Programming応用
type-parameterでのオーバーロードは厳しい
Template Meta Programming応用
そこで以下のような宣言を行う
extern void * enabler;
Template Meta Programming応用
extern void * enabler;
の定義は必要ない
enablerはextern修飾されているので外部リンケージを持つ
ということは
Template Meta Programming応用
non-type template-parameterに渡せるもの
外部リンケージのあるオブジェクト・extern修飾されたもの
non-type template-parameter
これはオーバーロード出来るtemplate<typename T, typename std::enable_if< std::is_integral<T>::value>::type *& = enabler>void f(T);
template<typename T, typename std::enable_if< std::is_pointer<T>::value>::type *& = enabler>void f(T);
Template Meta Programming応用
template<typename T, typename std::enable_if< std::is_integral<T>::value>::type *& = enabler>void f(T);
enable_ifを非型パラメータとして用いているのがポイント(参照型は非型パラメータになれる)
enablerは外部リンケージを持つので非型パラメータに渡す事が出来る
Template Meta Programming応用
わざわざ使わなくても十分なケースが多いしかし、必要になることもある
Template Meta Programming応用
index_tuple, index_range (integer_sequence) 整数列パラメータパックの生成と利用
Template Meta Programming応用
index_tuple idiomなどと呼ばれる・整数列のパラメータパックを生成するイ ディオム・C++14からはstd::integer_sequenceとして 同様の機能が標準ライブラリ入り・インデックスアクセス可能なデータ構造 の効率の良い要素の走査、リスト内包表 記相当の処理の実現
Template Meta Programming応用
・整数列パラメータを保持する型・整数列パラメータを生成するヘルパメタ 関数のセットで用いられる
Template Meta Programming応用
例えばNを正の整数としたとき
index_range<0, N>::typeがindex_tuple<0,1,2,3, ..., N-1>
と同じ型になるように実装する
Template Meta Programming応用
実装例(簡単でクソなO(N)オーダーでの実装)template<std::size_t... Indices>
struct index_tuple {};
template<std::size_t step, std::size_t last, class result = index_tuple<>, bool flag = step
>= last>
struct index_range
{
using type = result;
};
template<std::size_t step, std::size_t last, std::size_t... Indices>
struct index_range<step, last, index_tuple<Indices...>, false>
: index_range<step + 1, last, index_tuple<Indices..., step>>
{};
Template Meta Programming応用
実装例(簡単でクソなO(N)オーダーでの実装)template<std::size_t... Indices>
struct index_tuple {};
template<std::size_t step, std::size_t last, class result = index_tuple<>, bool flag = step
>= last>
struct index_range
{
using type = result;
};
template<std::size_t step, std::size_t last, std::size_t... Indices>
struct index_range<step, last, index_tuple<Indices...>, false>
: index_range<step + 1, last, index_tuple<Indices..., step>>
{};
Template Meta Programming応用
template<std::size_t... Indices>struct index_tuple {};
整数列パラメータを保持する型
例index_tuple<0,1,2,3,4,5> // 0,1,2,3,4,5を保持
Template Meta Programming応用
index_rangeは整数列パラメータを生成するヘルパメタ関数
・結果を返す定義(デフォルト定義)
・整数列パラメータを生成するフローの為 の部分特殊化
の2つの定義がある
Template Meta Programming応用
実装例(簡単でクソなO(N)オーダーでの実装)template<std::size_t... Indices>
struct index_tuple {};
template<std::size_t step, std::size_t last, class result = index_tuple<>, bool flag = step
>= last>
struct index_range
{
using type = result;
};
template<std::size_t step, std::size_t last, std::size_t... Indices>
struct index_range<step, last, index_tuple<Indices...>, false>
: index_range<step + 1, last, index_tuple<Indices..., step>>
{};
Template Meta Programming応用
結果を返すデフォルト定義template<std::size_t step,std::size_t last, class result = index_tuple<>,bool flag = first >= last>struct index_range{ using type = result;};
Template Meta Programming応用
パラメータの解説template< std::size_t step, // 現在処理している数(呼出は初期値を渡す)
std::size_t last, // 範囲の終わり(さきほどのN)
class result = index_tuple<>, // 結果型(デフォでindex_tuple<>)
bool flag = first >= last // 再帰の終了条件>struct index_range{ using type = result; // 型を定義して結果を返す};
Template Meta Programming応用
実装例(簡単でクソなO(N)オーダーでの実装)template<std::size_t... Indices>
struct index_tuple {};
template<std::size_t step, std::size_t last, class result = index_tuple<>, bool flag = step
>= last>
struct index_range
{
using type = result;
};
template<std::size_t step, std::size_t last, std::size_t... Indices>
struct index_range<step, last, index_tuple<Indices...>, false>
: index_range<step + 1, last, index_tuple<Indices..., step>>
{};
Template Meta Programming応用
生成フローの定義template<std::size_t step, // 現在処理している数std::size_t last, // 範囲の終わりstd::size_t... Indices // 結果の整数列パラメータ>struct index_range<step,last,index_tuple<Indices...>,false> : index_range<step + 1, last, index_tuple<Indices..., step>>{};
Template Meta Programming応用
生成フローの定義template<std::size_t step, // 現在処理している数std::size_t last, // 範囲の終わりstd::size_t... Indices // 結果の整数列パラメータ>struct index_range<step,last,index_tuple<Indices...>,false> // falseで特殊化: index_range<step + 1, last, index_tuple<Indices..., step>>{};
Template Meta Programming応用
デフォルト定義における以下の部分bool flag = first >= last // 再帰の終了条件がfalseになったときだけ部分特殊化された定義が使われる
すなわち生成フローの定義が使われる
Template Meta Programming応用
生成フローの定義template<std::size_t step, // 現在処理している数std::size_t last, // 範囲の終わりstd::size_t... Indices // 結果の整数列パラメータ>struct index_range<step,last,index_tuple<Indices...>, false> // falseで特殊化: index_range<step + 1, last, index_tuple<Indices..., step>>{};
Template Meta Programming応用
生成フローの定義template<std::size_t step, // 現在処理している数std::size_t last, // 範囲の終わりstd::size_t... Indices // 結果の整数列パラメータ>struct index_range<step,last,index_tuple<Indices...>, // 結果型はindex_tupleにIndicesを渡すfalse> // falseで特殊化: index_range<step + 1, last, index_tuple<Indices..., step>>{};
Template Meta Programming応用
template<
std::size_t step, // 現在処理している数std::size_t last, // 範囲の終わりstd::size_t... Indices // 結果の整数列パラメータ>
struct index_range<
step,
last,
index_tuple<Indices...>, // 結果型はindex_tupleにIndices渡すfalse> // falseで特殊化
: index_range<step + 1, last, index_tuple<Indices..., step>>// パラメータ違いのindex_rangeを継承している{};
Template Meta Programming応用
テンプレートパラメータ違いの自分自身を継承すること
: index_range<step + 1, last, index_tuple<Indices..., step>>
メタ関数における再帰を意味する
テンプレートパラメータは関数の引数相当
Template Meta Programming応用
: index_range<step + 1, last, index_tuple<Indices..., step>>
この部分ではstepを1進め可変長引数テンプレートパラメータの要素にstepを追加して再帰する
index_tupleに渡す引数を増やしながら再帰している
Template Meta Programming応用
結果を返すデフォルト定義に戻るtemplate<std::size_t step,std::size_t last, class result = index_tuple<>, // (index_tuple<Indices..., step>)bool flag = first >= last// stepがlastに達した時点で再帰が止まる>struct index_range{ using type = result; // 結果型};
Template Meta Programming応用
実用例
Template Meta Programming応用
配列クラスと値nを受けとり、全要素にnを加えた値の要素を持つ配列クラスを返す関数iota
template<class T, std::size_t N, std::size_t... Indices>constexpr auto iota_impl(const array<T, N> arr, const T n, index_tuple<Indices...>)-> array<T, N> { return array<T, N>{{(arr[Indices] + n)...}};}
template<class T, std::size_t N>constexpr auto iota(const array<T, N> arr, const T n) -> array<T, N> { return iota_impl(arr, n, typename index_range<0, N>::type());}
constexpr array<int, 9> arr1{{1,2,3,4,5,6,7,8,9}}; // 配列クラスのオブジェクト
constexpr auto arr2 = iota(arr1, 1); // iotaには配列クラスのオブジェクトと値nを渡してやる//結果としてarray<int, 9>{{2,3,4,5,6,7,8,9,10}}が欲しい
Template Meta Programming応用
template<class T, std::size_t N, std::size_t... Indices>constexpr auto iota_impl(const array<T, N> arr, const T n, index_tuple<Indices...>)-> array<T, N> { return array<T, N>{{(arr[Indices] + n)...}};}
template<class T, std::size_t N>constexpr auto iota(const array<T, N> arr, const T n) -> array<T, N> { return iota_impl(arr, n, typename index_range<0, N>::type());}
constexpr array<int, 9> arr1{{1,2,3,4,5,6,7,8,9}};constexpr auto arr2 = iota(arr1, 1); // array<int, 9>{{2,3,4,5,6,7,8,9,10}}
Tはintに、Nは9に推論される
Template Meta Programming応用
template<class T, std::size_t N, std::size_t... Indices>constexpr auto iota_impl(const array<T, N> arr, const T n, index_tuple<Indices...>)-> array<T, N> { return array<T, N>{{(arr[Indices] + n)...}};}
template<class T, std::size_t N>constexpr auto iota(const array<T, N> arr, const T n) -> array<T, N> { return iota_impl(arr, n, typename index_range<0, N>::type());}
constexpr array<int, 9> arr1{{1,2,3,4,5,6,7,8,9}};constexpr auto arr2 = iota(arr1, 1); // array<int, 9>{{2,3,4,5,6,7,8,9,10}}
この場合N = 9であるからindex_range<0, 9>::typeになる
Template Meta Programming応用
template<class T, std::size_t N, std::size_t... Indices>constexpr auto iota_impl(const array<T, N> arr, const T n, index_tuple<Indices...>)-> array<T, N> { return array<T, N>{{(arr[Indices] + n)...}};}
template<class T, std::size_t N>constexpr auto iota(const array<T, N> arr, const T n) -> array<T, N> { return iota_impl(arr, n, typename index_range<0, N>::type());}
constexpr array<int, 9> arr1{{1,2,3,4,5,6,7,8,9}};constexpr auto arr2 = iota(arr1, 1); // array<int, 9>{{2,3,4,5,6,7,8,9,10}}
index_range<0, 9>::typeはindex_tuple<0,1,2,3,4,5,6,7,8>である
Template Meta Programming応用
template<class T, std::size_t N, std::size_t... Indices>constexpr auto iota_impl(const array<T, N> arr, const T n, index_tuple<Indices...>)-> array<T, N> { return array<T, N>{{(arr[Indices] + n)...}};}
template<class T, std::size_t N>constexpr auto iota(const array<T, N> arr, const T n) -> array<T, N> { return iota_impl(arr, n, typename index_range<0, N>::type());}
constexpr array<int, 9> arr1{{1,2,3,4,5,6,7,8,9}};constexpr auto arr2 = iota(arr1, 1); // array<int, 9>{{2,3,4,5,6,7,8,9,10}}
結果としてindex_tuple<0,1,2,3,4,5,6,7,8>()がiota_implの引数として渡される
Template Meta Programming応用
template<class T, std::size_t N, std::size_t... Indices> // Indices is 0,1,2,3,4,5,6,7,8constexpr auto iota_impl(const array<T, N> arr, const T n, index_tuple<Indices...>)-> array<T, N> { return array<T, N>{{(arr[Indices] + n)...}};}
template<class T, std::size_t N>constexpr auto iota(const array<T, N> arr, const T n) -> array<T, N> { return iota_impl(arr, n, typename index_range<0, N>::type());}
constexpr array<int, 9> arr1{{1,2,3,4,5,6,7,8,9}};constexpr auto arr2 = iota(arr1, 1); // array<int, 9>{{2,3,4,5,6,7,8,9,10}}
このときIndicesは0,1,2,3,4,5,6,7,8に推論される
Template Meta Programming応用
template<class T, std::size_t N, std::size_t... Indices> // Indices is 0,1,2,3,4,5,6,7,8constexpr auto iota_impl(const array<T, N> arr, const T n, index_tuple<Indices...>)-> array<T, N> { return array<T, N>{{(arr[Indices] + n)...}};}
template<class T, std::size_t N>constexpr auto iota(const array<T, N> arr, const T n) -> array<T, N> { return iota_impl(arr, n, typename index_range<0, N>::type());}
constexpr array<int, 9> arr1{{1,2,3,4,5,6,7,8,9}};constexpr auto arr2 = iota(arr1, 1); // array<int, 9>{{2,3,4,5,6,7,8,9,10}}
最終的にarray<T, N>{{(arr[Indices] + n)...}}の部分はarray<int, 9>{{arr[0] + n, arr[1] + n, (中略)..., arr[8] + n}}
という風に展開される
Template Meta Programming応用
基本的にインデックスアクセス可能なデータ構造であれば適用出来る
Template Meta Programming応用
余談生成結果のメモ化
Template Meta Programming応用
template<std::size_t First, std::size_t Last>struct index_tuple_memo{ static constexpr auto value = typename index_range<First, Last>::type();};
index_tuple系の生成結果のメモ化を行う試みtypename index_range::type()の呼び出し時にindex_tuple_memo::valueを代わりに使う事で使いまわす2回目以降の呼び出しでO(1)が期待出来る
Template Meta Programming応用
・メモリ使用量が増える可能性・コンパイラの最適化によっては無意味 ・逆効果かも
同じ要素数で尚且つ数千要素のtuple展開を何度も行うような場合には有効かもしれないが通常は使わなくて良さそう
Template Meta Programming応用
index_rangeの実装には簡単でクソなO(N)オーダーでの実装の他に有用なO(log2(N))オーダーでの実装法も知られている
Template Meta Programming応用
線形オーダーでは扱える要素数が限られ、生成速度も遅い
のでSproutや標準ライブラリで後者の実装が使われている
Template Meta Programming応用
アイデア概要
例えばN = 14のときIndicesが0,1,2,3,4,5,6であればindex_tuple<Indices..., (Indices + N/2)...> を展開すればindex_tuple<0,1,2,3,4,5,6,7,8,9,10,11,12,13> が得られる
現実には実装はもっと面倒だが基本的には、このように前の結果を利用して要素数を倍々にしていくという考え
Template Meta Programming応用
詳しい実装に関してはsprout::index_rangeなどを見て欲しい
Template Meta Programming応用
Expression Template(ET)式テンプレート
Template Meta Programming応用
・式の評価を遅延させる・余計な一時オブジェクトの生成とコピーの 発生を防ぐ
Template Meta Programming応用
通常、巨大な行列の一時オブジェクトが生成される演算に対して、演算の結果ではなく演算を表す型オブジェクトを返す
Template Meta Programming応用
行列のA, B, Cの和A + B + C
ETを使わない実装では、A + Bで値を計算して一時オブジェクトを生成、更に一時オブジェクト + Cで値を計算して一時オブジェクトを生成という事が起こりうるその過程で巨大なオブジェクトのコピー、構築が起こりうる
Template Meta Programming応用
行列のA, B, Cの和Plus<Plus<Vector<A>, Vector<B>>, Vector<C>>
演算を型として表す、巨大な行列をコピーしたりしなくて済むので、途中式の段階で一時オブジェクトの生成コストが非常に小さくなる
Template Meta Programming応用
値はoperator=()が呼ばれた時に初めて評価される
Template Meta Programming応用
参考ETの簡単な実装法やデメリット等も含めて紹介されている
日本で一番分かりやすく書いたつもりのExpression Templateの説明http://d.hatena.ne.jp/Cryolite/20040506
などが分かりやすい
Template Meta Programming応用
おしながき
1. Templateの基本確認2. Template Meta Programmingとは3. Template Meta Programming入門4. Template Meta Programming応用5. その他付録6. まとめ7. 質疑応答
その他付録
1. TMPとは直接関係ないイディオムなど2. これからのTMPと関連イディオムなど
その他付録
型を受け取るユーザ定義リテラル
その他付録
整数リテラルと浮動小数点リテラルに限りchar...のテンプレート実引数として受け取ることが出来る
その他付録
template<char...>void operator "" _to_string() {};
12345_to_string; // template<‘1’,’2’,’3’,’4’,’5’>として渡される
3.14_to_string; // template<‘3’,’.’,’1’,,4>
その他付録#include <sprout/index_tuple.hpp>
template<char last>constexpr auto indices()-> typename sprout::index_range<0, last - '0'>::type { return sprout::index_range<0, last - '0'>::make();}
template<char... last>constexpr auto operator "" _indices() -> decltype(indices<last...>()) { static_assert(sizeof...(last) == 1, ""); return indices<last...>();}
int main() { static_assert(std::is_same<decltype(2_indices), sprout::index_tuple<0,1>>::value, "");}
ユーザ定義リテラルを使ったindex_tupleの生成みたいなサンプル(実際にはchar同士の減算等にはもう少し気を配った方がいい感じがしますが)
その他付録
型文字列の生成に使えるただし整数と浮動小数点のみ
ショージキ微妙だが今後に期待
その他付録
private namespace idiom detail等へのアクセスを制限する
その他付録namespace tmp { namespace { // 無名名前空間 namespace detail { // detailへはアクセスされたくない void f_impl() {} // privateな実装 } void f() { detail::f_impl(); } // publicなインタフェース } namespace detail {}; // detailのメンバへのアクセスを殺す} tmp::f(); // OK tmp::detail::f_impl(); // ルックアップに失敗しエラー
その他付録
無名名前空間が暗黙にusingされる事を逆手に取る
detailへのアクセスを殺せるので名前空間スコープで、メタ関数の内部実装にどうしてもアクセスされたくない場合には有効
その他付録
1. TMPとは直接関係ないイディオムなど2. これからのTMPと関連イディオムなど
その他付録
先程取り上げたinteger_sequenceの他にもある、C++14(所謂N3690辺り) / C++1y以降に入ると思われる機能とそれに関連する話(大雑把に未来の話です)
ただしTMPに割と関連しそうなものだけ
その他付録
C++14以降で(ほぼ確実に)入る機能
その他付録
constexprの制限緩和
その他付録
・副作用、制御構文が使えるようになる・ポインタ同士の減算が定数式になるなど
一部制限が増えている・a conversion from type cv void * to a pointer-to-object type;
C++11ではreinterpret_castが禁止されていたのにvoid*の型変換が禁止されていなかったが、これもダメに修正された
その他付録
ただし、constexpr関数では相変わらずエラーハンドリングが困難なので迂闊に副作用の濫用を行うと、例外安全性を保つのが難しいので注意が必要
その他、細かい事は規格等を参照して欲しい
その他付録
decltype(auto)
その他付録
decltype(auto)は、式をdecltype()の中に書いたのと同じ挙動になる
その他付録
江添さんの記事が分かりやすいhttp://cpplover.blogspot.jp/2013/08/decltypeauto.html
その他付録記事の例のみ引用する
int i = 0 ;int && f() ;
auto a1 = i ; // intdecltype(auto) a2 = i ; // int
auto b1 = (i) ; // intdecltype(auto) b2 = (i) ; // int &
auto c1 = f() ; // intdecltype(auto) c2 = f() ; // int &&
その他付録
関数の戻り値などに利用出来る
template < typename T, typename U >auto g( T const & t, U const & u )-> decltype( auto ) // decltype( f( t, u ) )と書かなくて済む{ return f( t, u ) ;}
その他付録
ジェネリックラムダ
その他付録
ラムダ式もテンプレート化出来るauto NumElements = []<int N>(auto (&a)[N]) { return N; };
その他付録
Variable Templates 変数テンプレート
その他付録
読んで字の如く、変数をテンプレート化する
その他付録
変数テンプレートtemplate<class T>constexpr T pi = static_cast<T>(3.14);
pi<int> == 3;pi<float> == 3.14;
異なる精度の値を得る
その他付録
変数テンプレートtemplate<class T1, T2>constexpr bool is_same_v = std::is_same<T1, T2>::value;
is_same_v<int, double>; // false
面倒な::valueを書かずに済む
その他付録
C++1y(ここではC++14よりも後を意味する)
以降で入るかもしれない提案
その他付録
Concepts Lite 軽量コンセプト
その他付録
C++11入りする予定だったConceptの軽量・改良版
幾つかペーパーのようなものが出ている模様
その他付録
プログラマにやさしい構文で型制約を行えるようになる
constexpr関数として型制約を定義する案など
使えるようになるのは当分先の話になりそうな気はします
その他付録
N3741Opaque Alias
その他付録
Strong typedef ある型から別の型を作る
using identifier = access-specifier type-id opaque-definition
その他付録
using age = private int; // intとは別の型として認識される// privateにより暗黙のキャストは許可しない
age age1 = static_cast<age>(20); // 明示的なキャストをするage age2 = 18; // Error ageはintではない
これまでのtypedefやusingではこのような事は出来ない
その他付録
オーバーロード等も有効になる
int f(int);age f(age);
別物として扱われる
その他付録
N3730Specializations and namespaces
その他付録
ネストされた名前空間を閉じなくても特殊化を書けるようにする提案
その他付録namespace A { namespace B { /* ... */ class C { /* ... */ }; }} // 一度名前空間を抜ける
namespace std { template<> // 特殊化を書く struct hash<A::B::C> { size_t operator()(A::B::C const &c) { /* ... */ } };}
namespace A { /* Reenter namespace I am using */ namespace B { /* ... */ }}
その他付録namespace A { namespace B { /* ... */ class C { /* ... */ }; template<> struct ::std::hash<C> { std::size_t operator()(C const &c) { /* ... */ } }; /* ... */ }}
このように書けるようになる
その他付録
N3728Packaging Parameter Packs
その他付録
パラメータパックを保持するための型リストをサポートする提案
パラメータパックが間違いなく扱いやすくなる
具体例はN3728ペーパー参照
その他付録
N3413Allowing arbitrary literal types for non-type template parameters
その他付録
非型パラメータとして任意のリテラル型を許容しようという提案
その他付録struct C { constexpr C(int v) : v(v) { } int v;};
template<C c> // !?struct X { int array[c.v];};
int main(){ X<C(42)> x;} C++の世界の法則が乱れる完全にヤバい提案
その他付録
N3761Proposing type_at<>
その他付録
パラメータパックのN番目の要素にアクセスする機能のメタ関数を標準に入れる提案
その他付録簡単な実装案が書かれている要するにN番目の要素に到達するまで線形再帰する
template <unsigned N, typename T, typename ...R>struct type_at { using type = typename type_at<N - 1, R...>::type;};
template <typename T, typename ...R>struct type_at<0, T, R...> { using type = T;};
その他付録簡単な実装案が書かれている要するにN番目の要素に到達するまで線形再帰する
template <unsigned N, typename T, typename ...R>auto value_at(T&&, R&&... r) -> decltype(auto) { return value_at<N - 1, R...>(std::forward<R>(r)...);}
template auto value_at(T&& t, R&&...) -> decltype(auto) { return std::forward<T>(t);}
その他付録
対数オーダーで実装可能なのでAuthorに実装の改良を提案する意見を送付した
その他付録
対数オーダーでの要素アクセスの実装
その他付録
次のようなメタ関数を用意するtemplate<std::size_t N, class T = void>using make_type_sequence = typename make_type_sequence_impl<N, T>::type;
// make N-1 Ts type_sequence<T, T, ..., T> O(log2(N))
implでindex_rangeのように型列を対数オーダーで生成する
その他付録
次のようなメタ関数を用意するtemplate<class T>struct type_wrapper{ using type = T;}; ただ型を保持するだけの型
その他付録template<class T, std::size_t N>struct type_at{private: template<template<class...> class Type, class... Args> static auto impl(type_wrapper<Type<Args...>>) -> typename type_at_impl<N, type_wrapper<Args>...>::type; public: using type = typename decltype(impl(type_wrapper<T>()))::type; }; 型リストを持つコンテナ型TとインデックスNを受け取る
その他付録template<class T, std::size_t N>struct type_at{private: template<template<class...> class Type, class... Args> static auto impl(type_wrapper<Type<Args...>>) -> typename type_at_impl<N, type_wrapper<Args>...>::type; public: using type = typename decltype(impl(type_wrapper<T>()))::type;}; type_wrapperに包んでTをimplに渡す
その他付録
type_wrapperの役割・インスタンス化出来ない型も処理出来るようにする
その他付録type_wrapperの役割
using type = typename decltype(impl(type_wrapper<T>()))::type;
例えばTがtemplate<class...> struct list; のようなインスタンス化不可能な型の場合
impl(list())はインスタンス化出来ずエラーになるがimpl(type_wrapper<T>())ならばインスタンス化出来て渡せる
その他付録
type_wrapperの役割・トップレベルのcv修飾を保持する
その他付録
type_wrapperの役割
impl(type_wrapper<T>())ならば値渡しの際もArgument
Deductionにおいて、Tのcv修飾情報が失われずに済む
その他付録template<class T, std::size_t N>struct type_at{private: template<template<class...> class Type, class... Args> static auto impl(type_wrapper<Type<Args...>>) -> typename type_at_impl<N, type_wrapper<Args>...>::type; public: using type = typename decltype(impl(type_wrapper<T>()))::type;}; template template parameterを使えばテンプレートクラスの持つ型リストを抽出する事が出来る
その他付録template<class T, std::size_t N>struct type_at{private: template<template<class...> class Type, class... Args> static auto impl(type_wrapper<Type<Args...>>) -> typename type_at_impl<N, type_wrapper<Args>...>::type; public: using type = typename decltype(impl(type_wrapper<T>()))::type;}; auto f() -> の形式の戻り値指定を使えば戻り値型部分でもparameter packを展開したり出来る
その他付録template<class T, std::size_t N>struct type_at{private: template<template<class...> class Type, class... Args> static auto impl(type_wrapper<Type<Args...>>) -> typename type_at_impl<N, type_wrapper<Args>...>::type; public: using type = typename decltype(impl(type_wrapper<T>()))::type;}; decltypeされる為だけに存在している関数であれば、関数定義は必要ない
その他付録template<class T, std::size_t N>struct type_at{private: template<template<class...> class Type, class... Args> static auto impl(type_wrapper<Type<Args...>>) -> typename type_at_impl<N, type_wrapper<Args>...>::type; public: using type = typename decltype(impl(type_wrapper<T>()))::type;}; またコンパイル時でもdecltypeするだけであればconstexpr修飾は必要無い
その他付録
むしろconstexpr修飾された関数は暗黙にinline修飾される上inline修飾された関数の定義がない場合clang等は警告を出すのでconstexpr修飾はしないべき
その他付録template<class T, std::size_t N>struct type_at{private: template<template<class...> class Type, class... Args> static auto impl(type_wrapper<Type<Args...>>) -> typename type_at_impl<N, type_wrapper<Args>...>::type; public: using type = typename decltype(impl(type_wrapper<T>()))::type;}; 更にメタ関数を呼び出す
その他付録template<std::size_t N, class... Types>struct type_at_impl{ using type = decltype(type_at_impl_impl< make_type_sequence<(N)> >::eval(static_cast< Types* >(nullptr)...) );};
type_sequence<void, void, void, ..., void>とN-1個のvoidを持つ型を対数オーダーで生成してテンプレートパラメータとして次の処理関数に渡す
その他付録template<std::size_t N, class... Types>struct type_at_impl{ using type = decltype(type_at_impl_impl< make_type_sequence<(N)> >::eval(static_cast< Types* >(nullptr)...) );};
nullptrを受け取ってきた型リストの型のポインタにキャストしながら展開して次の処理関数に引数として渡す
その他付録template<std::size_t N, class... Types>struct type_at_impl{ using type = decltype(type_at_impl_impl< make_type_sequence<(N)> >::eval(static_cast< Types* >(nullptr)...) );};
Typesはtype_wrapper<T>形式の型が入ったパラメータパックなのでその要素は必ずポインタにキャスト出来る
その他付録template<std::size_t N, class... Types>struct type_at_impl{ using type = decltype(type_at_impl_impl< make_type_sequence<(N)> >::eval(static_cast< Types* >(nullptr)...) );};
更に実装の無い関数を呼び、decltypeを行う
その他付録
template<class... Types>struct type_at_impl_impl<type_sequence<Types...>>{ template<class T> static T eval(Types*..., T*, ...);};
evalはテンプレートパラメータと関数引数の両方を受け取るdecltypeされる事が目的の実装の無い関数
その他付録
template<class... Types>struct type_at_impl_impl<type_sequence<Types...>>{ template<class T> static T eval(Types*..., T*, ...);};
TypesはN-1個のvoid型だからstatic_cast<Types*>(nullptr)...としてevalに渡された引数は手前からN-1個までのものはvoid*
という扱いで受け取られる(type erasureが働く)
その他付録template<class... Types>struct type_at_impl_impl<type_sequence<Types...>>{ template<class T> static T eval(Types*..., T*, ...);};
Tはtype_wrapper<元のコンテナの型リストのN番目の型>に推論されるtype_wrapperに包まれているのでN番目の型のcv修飾情報は欠落しない
その他付録
template<class... Types>struct type_at_impl_impl<type_sequence<Types...>>{ template<class T> static T eval(Types*..., T*, ...);};
残りの余分な引数はellipsisに受け取られるので結果として関数の戻り値型をTにすればdecltypeされた時にTを得る
その他付録template<class T, std::size_t N>struct type_at{private: template<template<class...> class Type, class... Args> static auto impl(type_wrapper<Type<Args...>>) -> typename type_at_impl<N, type_wrapper<Args>...>::type; public: using type = typename decltype(impl(type_wrapper<T>()))::type;}; 最終的に::typeでtype_wrapper<元のコンテナの型リストのN
番目の型>の中身である、元のコンテナの型リストのN番目の型が得られる
その他付録
補足decltype(expression)::typeはwell-formed
その他付録
この手法はNが大きい場合は有効であるが、Nが小さい場合は複雑な実装のメタ関数より線形実装の方が良い場合もある
その他付録実際type_atではNが小さい場合が多そうなので悩ましいという話になっている
が、とりあえず次のrevisionでこのような問題についても取り上げて貰える予定である
標準化による結果としてコンパイラマジックによるO(1)の実現というのもあり得るだろうがやはりオーダー自体は重要である
その他付録
この方法は同様にしてint,int,int...などのパラメータパック生成とconstexpr関数の組み合わせでvalue_atの実装にも適用出来る(つまりvalue_atも対数オーダー実装が可能)
その他付録
実装は
怜-toki-などを見て欲しいhttps://github.com/fimbul/toki
おしながき
1. Templateの基本確認2. Template Meta Programmingとは3. Template Meta Programming入門4. Template Meta Programming応用5. その他付録6. まとめ7. 質疑応答
今回、コンパイル時に行われるメタプログラミングのポテンシャル、型を扱うプログラミングというもののイメージ
Template Meta Programmingの有用性について少しでも伝わっていれば嬉しいです
まとめ
Template Meta Programmingのメリット・型を扱うプログラミングを行える・型検査でバグを実行前の段階で検出する・コンパイル時計算(実行時の処理を減らす)
・工夫でパフォーマンス向上(ET、遅延評価)
など
まとめ
Template Meta Programmingのデメリット・酷使すればコンパイル時間が長くなる・実装に実行時とは異なる技術知識が必要・不慣れだとデバッグがつらい(特にgcc)
(膨大なコンパイルエラーメッセージ)
・半分実装して途中経過を見るみたいな 事が難しいので脳内コンパイルも必要など
まとめ
デバッグ方法・static_assert
・実行時に結果を標準出力・実態のない型をインスタンス化する (エラーメッセージで型を見る)
・mpl::print(必ずしも上手くはいかない)
など
まとめ
スライドの内容はTemplate Meta Programming
の世界のたかだか氷山の一角に過ぎません
まとめ
世の中にはラムダ式(Boost.MPL)やレイトレーシング(Sprout)等もTMP(with constexpr)
での実装例がありますし、Metaparseのような(闇の)ライブラリもあります(コンパイル時haskellコンパイラ的なサムシングなど)
まとめ
入門から応用までなどと謳ったものの結局殆ど難しいことはろくに取り上げられず申し訳ないです、簡単な内容だけで結構な量になってしまっていたこともあり正直もっと応用的な内容にまで手が回りませんでした
まとめ
その実装だけで本一冊書けてしまう、TMP
の世界で最も有名であろうBoost.MPLのようなライブラリを一切取り上げられなかったのは残念です
まとめ
代わりに参考書籍を紹介します
まとめ
まとめBoost.MPLの解説書
・主にMPLの実装にまつ わる話など・TMPの汎用テクニック・やや情報は古い (C++03向け)
・邦訳もあるはず
まとめ
・Template関連本で本当 によく紹介されてい るので多分良書だと 思います・こちらもC++03向け・すいません自分は まだ読めてません
まとめ
TC++PL 4th Edition
・C++11に対応した最新 のTC++PL
・Templateの本というわ けではない
スライドの誤り等あれば教えて下さい
まとめ
きっとバグあります…
ご清聴ありがとうございました
時間がおしていなければ質疑応答に移ります(おしてたら後で個人的にお願いします)
まとめ
おしながき
1. Templateの基本確認2. Template Meta Programmingとは3. Template Meta Programming入門4. Template Meta Programming応用5. その他付録6. まとめ7. 質疑応答
ありがとうございました
終わり