実践C++応用講座こんにちは。テンプレートの明示的特殊化と部分的特殊化とオーバーロード(まとめて表現できる言葉が欲しいですね)を使って必要に応じて異なるテンプレートを定義する方法を解説しました。今回はその応用を1つ解説します。SFINAEとテンプレートの合せ技でよく出てくる「メンバの有無判定」を取り上げてみました。なるテンプレートを期待すると思います。頭だけ書いてみました。hasMember();しかし、これを作ることはできません。鋭い方は既にお分かりと思いますが、メンバ変数とメンバ関数のどちらとも型ではないのでテンプレートの型パラメータに与えることができません。非型パラメータならどうか?ですね。hasMember();これを書くこと自体は可能です。意味があるものは作れません。nMemberパラメータには有無判定したいメンバを指定する必要があります。そのメンバがある時は指定できますが、無い時は当然指定できません。エラーはSFINAEで無視されるのでは?と思いますよね。)残念ながらこのケースではSFINAEは働きません。次のhasMemberの呼び出しを実体化する際に、Foo::barがないのでエラーになるのですが、それがSFINAEで無視されると仮定します。このif文はどのようにコンパイルされるのでしょうか?SFINAEにより無視した後、何か他のエラーにならないものを選択できないとコンパイルできませんが、実引数の時点でエラーですのでどのテンプレート定義を選択してもエラーのままです。コンパイルする術がないのです。(解ってしまえば当たり前なのですが、長い間ここが良く解っていませんでした。member\n";}つまり、実引数はコンパイルに通るものを与えないといけません。いうことは、指定のクラスが必要なメンバを持っていないとhasMemberテンプレートに与えることができないわけです。そのため、hasMemberを呼び出す時点でそのメンバ変数があることが判っているクラスしか指定できないのです。役に立たないhasMemberですね。メンバ名>::valueのようなテンプレートを実現するのは無理ではないかと思います。2.ではどうするのか?名前パラメータ的なものって、実は#defineで定義するマクロなら可能です。構文とは全く無関係です。ソース・コードの文字を置き換える機能ですから。それが型名だろうが変数名だろうが気にしません。マクロを使ってメンバーの有無を判定するテンプレートを作るわけです。メタ・メタ・プログラミングテンプレートをプログラムすることははクラスや関数を作り出すプログラムを書くことですから「メタ・プログラミング」と呼ばれます。そのテンプレートを作り出すマクロを書くことは「メタ・メタ・プログラミング」かも。この1~2年ほどの夢だったのですが、このブログを書くために色々考えてやっと成功しました。}使うためには、事前準備が必要です。テンプレートは使う前に定義が必要ですから使う前に準備が必要なのです。判定したいメンバ名を指定してテンプレート定義を生成します。HAS_MEMBER(var);HAS_MEMBER(func);これにより、has_var<クラス>テンプレートとhas_func<クラス>テンプレートが定義されます。これらのテンプレートは指定したクラスがvarやfuncを持っていたらvalueがtrueになります。"\n";}has_var<int>::value=0has_var<Foo>::value=0has_var<Bar>::value=1has_func<int>::value=0has_func<Foo>::value=0has_func<Bar>::value=13.has_varテンプレートの解説まず、マクロは展開される時、単純に仮引数が与えられた実引数に置き換えられます。##はマクロの中だけで使えるプリプロセッサ演算子で前後の文字列を単純に繋げます。従って、HAS_MEMBER(var);は、次のようなテンプレート定義に展開されます。decltype(check(mClass))::value;}ポイントは「関数テンプレートcheck()とその通常関数check(...)によるオーバーロード(4-6行)」、および、「その呼び出し(9行)」です。厳密にはメンバ関数テンプレートですが、この使い方の場合は今まで解説してきた関数テンプレートと同じように振る舞いますので気にしないでください。(あああ、そういえばメンバ・テンプレートの解説を忘れてます。)3-1.オーバーロード関数の定義まず、関数テンプレートcheck(tInnerClass*)は、通常関数check(...)とオーバーロードされています。通常関数でオーバーロードされた場合、一般には通常関数の方が優先的に選択されるのですが、可変長引数(...)の場合は逆に優先順位が落ちるようです。可変長引数はなんでも受け入れますから、優先順位が高いと他の全てが選択されませんのでそのように決めたのだと思います。この2つのcheck()は、関数テンプレートに適合する場合は関数テンプレートが選択されます。3-2.SFINAEによるオーバーロードの解決9行目にcheck(mClass)の呼び出しが記述されています。この呼び出しでどちらかのcheck()が実体化されます。mClassは8行目で指定クラスへのポインタとして定義されていますので渡しているパラメータはtClass*型です。前回解説したように関数テンプレートはプライマリー・テンプレートをオーバーロードできます。その際、非型パラメータを指定し、その非型パラメータの記述部分でstd::enable_ifを用いて、エラーが発生したら選に漏れるように設計していました。今回は、プライマリー・テンプレート自体はオーバーロードしていないため、ちょっと手抜きして非型パラメータのデフォルト値でエラーを発生させています。0)はカンマで区切って2つの値を記述しています。この場合、カンマで区切った最後の値が有効になります。途中のものは評価されますが、結果は無視されます。dummyは0がデフォルト値となります。途中の&tInnerClass::varはメンバvarへのポインタです。static・非staticどれでもOKです。)つまり、tInnerClassがメンバvarを持っていればエラーになりません。逆にvarを持っていなければエラーになります。エラーになった場合、代替案のcheck(...)が控えていますから、SFINAEによりcheck(...)が選択されます。エラーにならなければ関数テンプレートの方のcheck()が選択されます。これにより、9行目のcheck(mClass)は、ポインタmClassが指すクラスがメンバvarを持ってなければ通常関数のcheck(...)、持っていれば関数テンプレートのcheck()になります。3-3.ちょっと不思議な戻り値の型の取り出しそのcheck(mClass)は、更にdecltype()に渡されています。前回軽く説明したように「decltyeはその引数の「型」を返します。