template meta programming入門から応用まで

Post on 31-May-2015

6.076 Views

Category:

Documents

2 Downloads

Preview:

Click to see full reader

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. 質疑応答

ありがとうございました

終わり

top related