C++ SFINAE
SFINAE とは
SFINAE は、Substitution Failure Is Not An Error の略です
これは、テンプレートの展開途中に文法違反になるような候補があったとしても、他の候補があるのであればその文法違反はエラーとせずに無視するという仕様です
と言っても何のことを言っているのかわからないかもしれませんが、大丈夫です
具体的な例を見れば SFINAE というのは実はとても簡単な仕組みに SFINAE という名前を付けているだけなんだということがわかると思います
標準での言及箇所は・・・・・・わかりません
標準の .pdf を substitution で検索すると 14.8 辺りにそれっぽいところはあります
n1905 14 Templates 14.8 Function template specializations 14.8.2 Template argument deduction 2 ... If a substitution in a template parameter or in the function type of the function template results in an invalid type, type deduction fails.
SFINAE の例
ここに配列とコンテナの先頭と末尾を返してくれる begin, end というテンプレート関数があります
特に何でもないもののように見えると思いますが、実はここには SFINAE の仕組みが働いています
// 配列 template<typename value_type_, int _size> const value_type_* begin( const value_type_ (&a)[_size] ){ debug_printf( "配列:const begin\n" ); return &a[0]; } template<typename value_type_, int _size> const value_type_* end( const value_type_ (&a)[_size] ){ debug_printf( "配列:const end\n" ); return &a[_size]; } template<typename value_type_, int _size> value_type_* begin( value_type_ (&a)[_size] ){ debug_printf( "配列:begin\n" ); return &a[0]; } template<typename value_type_, int _size> value_type_* end( value_type_ (&a)[_size] ){ debug_printf( "配列:end\n" ); return &a[_size]; } // コンテナ template<typename T> typename T::const_iterator begin( const T& container ){ debug_printf( "コンテナ:const begin\n" ); return container.begin(); } template<typename T> typename T::const_iterator end( const T& container ){ debug_printf( "コンテナ:const end\n" ); return container.end(); } template<typename T> typename T::iterator begin( T& container ){ debug_printf( "コンテナ:begin\n" ); return container.begin(); } template<typename T> typename T::iterator end( T& container ){ debug_printf( "コンテナ:end\n" ); return container.end(); } int main(){ const int a[10] = {}; std::vector<int> b; if( begin( b ) == end( b )){ debug_printf( "begin( b ) == end( b )\n" ); } else{ debug_printf( "begin( b ) != end( b )\n" ); } if( begin( a ) == end( a )){ debug_printf( "begin( a ) == end( a )\n" ); } else{ debug_printf( "begin( a ) != end( a )\n" ); } return 0; }
この begin, end の実装が配列用だけ、コンテナ用だけであった場合を考えてみましょう
まずコンテナ用の実装だけであった場合には実引数に配列を渡してしまうと、配列には iterator や const_iterator といった型も begin や end といったメンバ関数も無いのでコンパイルエラーになってしまいます
配列とコンテナをどちらも渡したいということだと、これでは困りますね
一方、配列用の実装だけであった場合には実引数に配列を渡してもコンテナを渡してもどちらも [] で要素にアクセスすることができるので何も問題はありません
SFINAE の仕組みがないなら配列とコンテナの両方を渡す目的にはこちらを使うしかありません
配列用の実装とコンテナ用の実装と両方がある場合にもコンテナ用の実装に配列を渡してしまうとコンパイルエラーになってしまうということ自体は何も変わらないのですが、実際には両方の実装がある場合には適切な実装が選択されるので配列がコンテナ用の実装に渡されるというようなことは起きず、コンパイルエラーにはなりません
これはコンパイラが適切な実装を選んでくれる仕組みがあるからです
このコンパイラが適切な実装を選んでくれるという機能は言い換えると、コンパイルエラーになるような実装があっても他に適切な実装がある場合には他の実装で出るコンパイルエラーは無視するということです
これを英語で言ったのが Substitution Failure Is Not An Error (SFINAE) です
SFINAE とテンプレートメタプログラミング
SFINAE は本来は上記の例のような問題を解決するために導入されたルールなのですが、現在はテンプレートメタプログラミングの方面でも様々に利用されています
SFINAE の仕組みを使って例えば次のように、指定した型が特定のメンバ変数を持っているかどうかを確認することもできます
// 指定された型 T は int 型の foo という名前の非 static メンバ変数を持っているか template<typename T> class has_int_foo { struct detail { struct sfinae_helper_result_type {}; template<typename T,int T::*> struct sfinae_helper_t { typedef sfinae_helper_result_type type; }; template<typename T,typename U = sfinae_helper_result_type> struct has_int_foo { static const bool value = false; }; template<typename T> struct has_int_foo<T,typename sfinae_helper_t<T,&T::foo>::type> { static const bool value = true; }; }; public: static const bool value = detail::has_int_foo<T>::value; };
ここで注目してほしいのは detail::has_int_foo の第二引数が detail::sfinae_helper_t<T,&T::foo>::type となっているところです
sfinae_helper_t の第二引数は int T::* と宣言されているので、型 T が foo という名前の int 型の非 static メンバ変数を持っているのでなければコンパイルエラーになります
コンパイルエラーにならない場合には sfinae_helper_t の type は常に sfinae_helper_result_type ですから detail::has_int_foo の value は true になります
sfinae_helper_t の評価がコンパイルエラーになる場合には detail::has_int_foo の value は false になります
これは次のようなテンプレートを見ると簡単です
struct foo_default_type {}; template<typename T,typename U = foo_default_type> struct foo_t { static const int value = 0; }; template<typename T> struct foo_t<T,foo_default_type> { static const int value = 1; };
foo_t<T>::value は 1 でしょうか 0 でしょうか
この場合、U のデフォルト引数が foo_default_type なので foo_t<T> は foo_t<T,foo_default_type> に展開されます
foo_t<T,foo_default_type> は特殊化されている実装があるので、それが採用されます
従って foo_t<T>::value は常に 1 になります
これはどうでしょうか
struct foo_default_type {}; template<typename T,int T::*> struct foo_sfinae_helper_t { typedef foo_default_type type; }; template<typename T,typename U = foo_default_type> struct foo_t { static const int value = 0; }; template<typename T> struct foo_t<T,foo_sfinae_helper_t<T,&T::foo>::type> { static const int value = 1; };
特殊化の foo_default_type だったところが foo_sfinae_helper_t<T,&T::foo>::type になりましたが他は同じです
ですから深く考えなくても foo_t
ただし、正常にコンパイルできる場合は、です
foo_sfinae_helper_t<T,&T::foo>::type がコンパイルできないなら特殊化された実装は採用されません
ここで SFINAE の決まりにより、他の候補があるのでそちらの実装が採用され、特殊化された実装でのエラーは無視されます
そうなると foo_t<T>::value は 0 です
このように SFINAE という仕組みを使うとコンパイルエラーになるかならないかという判定器を作り出すことができるようになりました
今日ではこれがテンプレートメタプログラミングで様々に利用されるようになっています
SFINAE という仕組みはいつの間にか C++ の基盤技術の一つになっているので覚えておくといいと思います
試しに、指定した型が特定の型を持っているかどうかを確認する判定器を書いてみましょう
指定した型が特定の型を持っているか
指定した型が特定の型を持っているかどうかを確認するには、例えば次のようにします
// 指定された型 T は value_type という名前の型を持っているか template<typename T> class has_value_type { struct detail { struct true_type { int unused[1]; }; struct false_type { true_type unused[2]; }; template<typename U> static true_type sfinae_helper( typename U::value_type* ); template<typename U> static false_type sfinae_helper(...); template<typename T> struct has_value_type { static const bool value = ( sizeof( sfinae_helper<T>(0)) != sizeof( false_type )); }; }; public: static const bool value = detail::has_value_type<T>::value; };
指定した型が特定の static なメンバ関数を持っているか
指定のクラスが create_instance という static メソッドを持っていたらそれでインスタンスを作り、持っていなければ new でインスタンスを作るというようなことも、SFINAE の仕組みを利用すると実現できます
template<typename T> class create_instance_t { struct detail { struct sfinae_helper_result_type {}; template<typename T,T* (*)()> struct sfinae_helper_t { typedef sfinae_helper_result_type type; }; template<typename T,typename U = sfinae_helper_result_type> struct create_instance_t { static T* create_instance(){ return new T; } }; template<typename T> struct create_instance_t<T,typename sfinae_helper_t<T,&T::create_instance>::type> { static T* create_instance(){ return T::create_instance(); } }; }; public: static T* create_instance(){ return detail::create_instance_t<T>::create_instance(); } }; struct CFoo { static CFoo* create_instance(){ debug_printf( "CFoo::create_instance\n" ); return new CFoo; } private: CFoo(){ debug_printf( "CFoo::CFoo\n" ); } }; struct CBar { CBar(){ debug_printf( "CBar::CBar\n" ); } }; int main(){ //CFoo* foo = new CFoo; // CFoo::CFoo が private だから直接 new することはできない CFoo* foo = create_instance_t<CFoo>::create_instance(); CBar* bar = create_instance_t<CBar>::create_instance(); delete bar; delete foo; return 0; }
指定された型が仕様に合うか
さまざまな条件を組み合わせることもできます
下記の例は派生クラスが基底クラスの非仮想関数の名前を隠していないかどうかをチェックするものです
struct my_concept { struct violation {}; // 派生クラスが fooメンバ関数を持っていたらダメ template<typename T,void (T::*)()> struct final_foo_helper_t { typedef violation type; }; template<typename T,typename U = violation> struct final_foo {}; template<typename T> struct final_foo<T,typename final_foo_helper_t<T,&T::foo>::type>; // 派生クラスが barメンバ関数を持っていたらダメ template<typename T,void (T::*)()> struct final_bar_helper_t { typedef violation type; }; template<typename T,typename U = violation> struct final_bar {}; template<typename T> struct final_bar<T,typename final_bar_helper_t<T,&T::bar>::type>; }; class cfoo; template<> class my_concept::final_foo<cfoo> {}; template<> class my_concept::final_bar<cfoo> {}; template<typename T> class my_resquires : my_concept::final_foo<T>, my_concept::final_bar<T> {}; template<typename T> class my_checker : my_resquires<T> {}; class cfoo { public: void foo(){ debug_printf( "foo\n" ); } void bar(){ debug_printf( "bar\n" ); } cfoo(){ } }; class cbar : public cfoo { public: cbar(): cfoo() { } }; class cbaz : public cfoo { public: void foo(){ debug_printf( "baz\n" ); } cbaz(): cfoo() { } }; class cqux : public cfoo { public: void bar(){ debug_printf( "qux\n" ); } cqux(): cfoo() { } }; static_assert( sizeof( my_checker<cfoo> ), "cfoo: concept violation" ); static_assert( sizeof( my_checker<cbar> ), "cbar: concept violation" ); static_assert( sizeof( my_checker<cbaz> ), "cbaz: concept violation" ); // foo を持っているからダメ static_assert( sizeof( my_checker<cqux> ), "cqux: concept violation" ); // bar を持っているからダメ int main(){ cfoo foo; cbar bar; cbaz baz; cqux qux; foo.foo(); bar.foo(); baz.foo(); qux.bar(); return 0; }
この実装は可変長テンプレート引数(variadic template)を使えるなら次のようにすることもできます
template<typename... concepts> class my_resquires : concepts... {}; template<typename T> class my_checker : my_resquires<my_concept::final_foo<T>, my_concept::final_bar<T>> {}; static_assert( sizeof( my_checker<cfoo> ), "cfoo: concept violation" ); static_assert( sizeof( my_checker<cbar> ), "cbar: concept violation" ); static_assert( sizeof( my_checker<cbaz> ), "cbaz: concept violation" ); // foo を持っているからダメ static_assert( sizeof( my_checker<cqux> ), "cqux: concept violation" ); // bar を持っているからダメ
また static_assert を有効に使えるように不完全型を利用したエラーの代わりに評価結果を bool値で返すようにすることもできます
struct my_concept { struct violation {}; struct false_type { static const bool value = false; }; struct true_type { static const bool value = true; }; // foo template<typename T,void (T::*)()> struct final_foo_helper_t { typedef violation type; }; template<typename T,typename U = violation> struct final_foo : public true_type {}; template<typename T> struct final_foo<T,typename final_foo_helper_t<T,&T::foo>::type> : public false_type {}; // bar template<typename T,void (T::*)()> struct final_bar_helper_t { typedef violation type; }; template<typename T,typename U = violation> struct final_bar : public true_type {}; template<typename T> struct final_bar<T,typename final_bar_helper_t<T,&T::bar>::type> : public false_type {}; }; template<typename... concepts> struct my_requires { template<int, typename concept, typename... concepts_> struct my_requires_next { static const bool value = concept::value && my_requires_next<sizeof...(concepts_),concepts_...>::value; }; template<typename concept> struct my_requires_next<1,concept> { static const bool value = concept::value; }; static const bool value = my_requires_next<sizeof...(concepts),concepts...>::value; }; class cfoo; template<> class my_concept::final_foo<cfoo> : public my_concept::true_type {}; template<> class my_concept::final_bar<cfoo> : public my_concept::true_type {}; template<typename T> struct my_checker : public my_requires<my_concept::final_foo<T>,my_concept::final_bar<T>> {}; static_assert( my_checker<cfoo>::value, "cfoo: concept violation" ); static_assert( my_checker<cbar>::value, "cbar: concept violation" ); static_assert( my_checker<cbaz>::value, "cbaz: concept violation" ); // foo を持っているからダメ static_assert( my_checker<cqux>::value, "cqux: concept violation" ); // bar を持っているからダメ
余談
この SFINAE のようなプログラミングの新しい言葉は覚えるだけでも大変だと思います
この SFINAE のようなアクロニムは元の言葉を知らない人には何も伝わらないという大きな欠点があります
漢字一文字は英単語に相当するようなものなので略語にしても意味が残りますが、アルファベットはそうではありませんからね
プログラミングの世界では、プログラミング、プログラマ、コンピューター、ソフトウェア、アルゴリズムなどなど多くの外国語が訳されないまま使われているので皆さんも覚えるのが大変でしょう
漢字も外国語といえば外国語なので別に外国語のまま理解できるようになってしまってもいいのかもしれませんが、元々外国語であった情報や論理などといった言葉を今外国語で考えなくていいのは訳語があるお蔭です
皆さんも自分なりの訳語を考えてみてはどうでしょう
いつかそれが世に広まって後世のプログラマの苦労が少しは減るかもしれませんよ