Tag Archives: template

式の中に出てくるtypenameやtemplate

これらのキーワードは次のトークンがタイプなのかテンプレートなのかを指定する。テンプレート関数内でテンプレート引数Tに依存するトークンが出てきたとき、コンパイラはそのトークンがタイプなのかテンプレートなのか関数なのか変数なのかわからない。

Aが変数なら、例えば0に置き換えてみると以下のようになる。

<や>は小なりと大なりと解釈され、有効な式になる。

Aが関数の場合は<0>はテンプレート引数となりxを引数とする関数コールになる。

Aがテンプレートクラスの場合はそのクラスインスタンスxの定義になる。

テンプレートが実体化されたときにわかりそうだが、意図した動作と違う時にエラーの発見が難しくもなりそうなので、Tに依存するトークンでそれがあいまいな場合(どんな時あいまいなのかはよくわからない)はtypenameやtemplateといったキーワードをつける約束になっているようだ。この例の場合はAはintなどの基本タイプにはなれない。

このような用途で使うのがtypenameとtemplate。

typename

T::Aがタイプの場合に指定する。これはよく使う。意味不明なエラーが出た場合につけると直ることが多い。

template

カギカッコなしのtemplateは他の使い方もあるが、ここではTに依存する(Tの中にある)テンプレートクラスやテンプレート関数を指定するときにつけるようだ。テンプレートクラスの場合はタイプでもあるので、両方つけることもある。

こういうものをつけなければならないときはusingを使って別名にしておいた方がいいだろう。

実験したまとめ

Aが変数の場合

typenanmeもtemplateもつけてはいけない(つけるとエラー)

Aが関数テンプレートの場合

typenameとtemplateと両方つけるか、何もつけない(typenameだけだとエラー)

Aがクラステンプレートの場合

両方つけないとエラー

ソース

https://github.com/ambiesoft/blogprogs/tree/master/6151/templatetypename

参考にした動画

to_stringとto_wstring

to_stringとto_wstringはintなどを文字列にして返す標準ライブラリ関数。便利なのだが、charとwchar_tで分かれているので汎用コードを書きたいときにそのまま書きたくない。Windowsはもはやwchar_tが主流だしLinuxではcharが主流でwchar_tは4バイトになることが多い。よってこれらをそのまま使うと汎用性がなくなる。

テンプレート関数

これらの関数をテンプレート関数にしたいとき、テンプレート引数は2つになり、最初の引数はcharかwchar_t、次の引数はintやdoubleとなる。テンプレート関数は部分特殊化ができないので、これらの組み合わせすべて書くしかなくなって効率が悪い。

テンプレートクラス

テンプレートクラスは部分特殊化ができるので、これを利用して汎用関数をつくりたい。文字列クラスのデフォルトをWindowsならwchar_tとしそれ以外ならcharとして扱えば同じ関数記述にできる。

汎用テンプレートクラスの定義

これは実体化されることを想定してないので、static_assert常に失敗。
次にcharの部分特殊化

同様にwchar_t

ここまではユーザは意識しないコード。次がユーザが呼ぶ関数。

SYSTEM_CHAR_TYPEは事前にtypedefされているcharかwchar_t。

テストコード

bool追加

to_stringにはboolがなく、intとして解釈されるようなのでboolも完全特殊化で定義

ソース:https://github.com/ambiesoft/lsMisc/blob/master/stdosd/stdosd.h

考察

C++にはもともとstreamがあって、それを使えばすべて汎用関数で書けそう。しかしto_stringとかの実装はそうなってないようなのでスルー。

これとは逆の関数。文字列からintなどに変える関数は引数に文字列しか入らないので型の推論ができないのでそれぞれの型ごとに別関数にするしかないものと思われる。

C++ テンプレート関数をソース・ファイルに書く

前提

まだ完全にはわからない

Tが汎用の場合

std::vector<T>などのTに何でも入れられることを前提にしてつくられているクラスはヘッダに書くしかないと思う。

Tが一部の型のみの場合

この場合はソースファイルにかける。

ここでは文字列の長さをを返す関数でchar*とwchar_t*をとるGetStringLength()を考える。

ヘッダ

最初のテンプレート関数は汎用のTを受け取る。もしテンプレート関数が実体化されたときはstatic_assertでエラーになるようにしている(sizeof(T)は決して0にならない。falseを指定してしまうと実体化しなくてもエラーになってしまう(コンパイラ依存))。char*とwchar_t*で特殊化するので、それ以外のポインタで呼ばれたときに実体化される。

次の2つは特殊化の宣言。定義はソースに書く。
次の2つは特殊化が実体化されるときの場所の指定。externでどこかにそれがあることを示している。

ソース

最初の2つは特殊化の定義。
次の2つは特殊化が定義されるときここに実体化されることを意味する。

main.cpp

今回の場合は実装がほぼないのでテンプレートにする意味があんまりないが、2つの実装がほぼ同じで、一部だけ書き換えたいときは汎用Tの方に実装を書くこともできる。その場合にこのテンプレートを使うこともできる。

Tが一部の型のみの場合(バージョン2)

ヘッダー

ソース&main.cpp

ソース:https://github.com/ambiesoft/blogprogs/tree/master/6038

考察

テンプレート関数の定義をソース・ファイルに書くと、他のソース・ファイルからは見ることができないのでリンカーでつなげることになる。よって鉤括弧なしのtemplateは実装を実体化し外部に公開して他のソース・ファイルから見えるようにしているのだと思われる。

鉤括弧ありのテンプレートはあくまでコンパイルフェーズで解決するものであり、宣言だけのテンプレートを実体化しても宣言だけになり、それがリンクされることもないのだと思われる。

C++のテンプレート関数で特定の型でエラーにする

std::is_class

is_class<T>を使うとTがクラス(構造体含む)かどうか判定してくれる。

テンプレート関数funcクラスや、クラスへのポインターや、クラスへのポインターのポインターを渡すとエラーになる。 ただしクラスへのポインターのポインターのポインターを渡すことはできる。

ソース

https://github.com/ambiesoft/blogprogs/tree/master/6022/isClass

テンプレートのvariadic引数で型をチェックする

普通の型チェック

Tがポインタだとエラーになる。

variadicの場合

最初の引数の型Tに対してチェックしている。CheckKataに渡される引数がwchar_t*だったりポインタのポインタだったらエラーになる。ただwchar_tはセーフにしている。

remove_cvなどの挙動がいまいちよくわからないのでこの書き方であっているのか不明。以下のようなコードで実行時の型を表示してくれる。

is_sameは渡された2つの型が同じならtrue
decayは配列参照などをポインタにしてくれる。
remove_cvconstvolatileを除去
remove_pointerはポインタを1つ除去する。

_vで終わるのはvalue,is_same::valueなどと同じ。
_tで終わるのはtype,remove_cv::typeなどと同じ。

ソース:https://github.com/ambiesoft/blogprogs/tree/master/6006/ConsoleApplication1