クラス内にあるtypedefが存在するか調べる
あるクラスにあるtypedefが存在するか否かを調べる方法はないかな?
とか悩んでいてTwitterでつぶやいたらフォロワーさんがそれSFINAEでできるよと教えてくれた。
SFINAEとは「Substitution Failure Is Not An Error」のことで「置き換えの失敗はエラーにならない」というものらしい。
これは満たしているかどうか調べたい条件のパターンマッチを書いておいて、仮にそれを満たさない(今回の場合はtypedefが存在しない)場合は別のパターンマッチが試され...ってのが繰り返されパターンマッチのいずれかに適合しさえすれば(たとえそのパターンマッチのいくつか単体では不正であったとしても)
コンパイルエラーにならないということ。(もちろんすべてを満たさない場合はコンパイルエラーとなる)
つまり「ある型が条件を満たすかどうか」の判断に使える。
今回の目的のために使ったもの(を若干改造した)サンプルは以下のとおり。
これではParameterというものがクラス内でtypedefして定義されているか
を調べるというものだ。
#include<iostream> struct yes{}; struct no{}; //定義されているかどうかを調べるためのクラス template<typename TargetClass> class definedParameterTypedef{ public: private: //ここがキモ template <typename T> static yes check(typename T::Parameter*); template <typename> static no check(...); public: typedef decltype(check<TargetClass>(nullptr)) Result; }; //デバッグ用 template<typename T> void printType(){ std::cout<<"Other"<<std::endl; } template<> void printType<yes>(){ std::cout<<"yes"<<std::endl; } template<> void printType<no>(){ std::cout<<"no"<<std::endl; } struct Hoge{ typedef int Parameter; }; int main(){ printType<definedParameterTypedef<int>::Result>(); printType<definedParameterTypedef<Hoge>::Result>(); return 0; }
テンプレートメンバ関数checkの引数によるパターンマッチによって
返り値の型が異なるメンバ関数が生成される。(...)のほうではどんなものでも受け取るため、パターンマッチがはずれることはない。また、なぜポインタにしているかということだが、こうすれば定数であるnullptrを渡しておけばコンパイル時に解決できるからだ。これがポインタでないとParameterとしてtypedefされているクラスのインスタンスを生成する必要があり、これはコンパイル時に行えるとは限らない。
問題はこの返り値の型をどうやって取り出すかということだが、C++11からはdecltypeがあるのでそれを使った。はじめてdecltypeが便利だなと思った瞬間。