C++ テンプレートクラスにあるテンプレートメンバ関数の特殊化
はじめに
特殊化していないテンプレートクラスの中にある内側のテンプレートだけを特殊化するということはできません
標準では次のように言っています
14 Templates 14.7 Template instantiation and specialization 14.7.3 Explicit specialization 18 In an explicit specialization declaration for a member of a class template or a member template that appears in namespace scope, the member template and some of its enclosing class templates may remain unspecialized, except that the declaration shall not explicitly specialize a class member template if its enclosing class templates are not explicitly specialized as well. In such explicit specialization declaration, the keyword template followed by a template-parameter-list shall be provided instead of the template<> preceding the explicit specialization declaration of the member. The types of the template-parameters in the template-parameter-list shall be the same as those specified in the primary template definition. [ Example: template<class T1> class A { template<class T2> class B { template<class T3> void mf1( T3 ); void mf2(); }; }; template<> template<class X> class A<int>::B { template <class T> void mf1( T ); }; template<> template<> template<class T> void A<int>::B<double>::mf1( T t ){} template<class Y> template<> void A<Y>::B<double>::mf2(){} // ill-formed; B<double> is specialized but its enclosing class template A is not -end example ]
外側のテンプレートだけを特殊化するということはできますが、内側のテンプレートだけを特殊化するということはできません
どうしても内側のテンプレートだけを特殊化したいという場合には、その部分を別のテンプレートにして外に出してしまいましょう
そうすれば「内側のテンプレート」ではなくなるので問題なく特殊化できるようになります
テンプレートクラスにあるテンプレートメンバ関数を特殊化したい
次のようなクラス cfoo をテンプレート化して char, wchar_t のどちらでも使えるようにしたいという場合を考えてみましょう
class cfoo { std::string m_text; public: void append( const char* type, const char* name ){ m_text.append( type ); m_text.push_back( '\t' ); m_text.append( name ); m_text.append( ";\n" ); } template<typename value_type> void append( const char* name ); cfoo():m_text(){} }; template<> void cfoo::append<int>( const char* name ){ append( "int", name ); } template<> void cfoo::append<float>( const char* name ){ append( "float", name ); } int main(){ cfoo foo; foo.append<int>( "aaa" ); foo.append<float>( "bbb" ); return 0; }
まずはリテラルの排除
元のクラスには文字リテラルと文字列リテラルが含まれていますから、まずはこれをテンプレート引数に応じて char, wchar_t に切り替えられるようにする必要がありますね
これは「C++ テンプレート引数に応じた場合分け」で既に紹介しましたから問題ないでしょう
template<typename char_type> inline const char_type* tstring_cast_( const char* s,const wchar_t* ){ return s; } template<> inline const wchar_t* tstring_cast_<wchar_t>( const char*, const wchar_t* ws ){ return ws; } #define tstring_cast(char_type,s) tstring_cast_<char_type>(s,L##s) template<typename char_type,char c,wchar_t> struct tchar_cast_t { static const char_type value = c; }; template<char c,wchar_t wc> struct tchar_cast_t<wchar_t,c,wc> { static const wchar_t value = wc; }; #define tchar_cast(char_type,c) tchar_cast_t<char_type,c,L##c>::value
テンプレートの特殊化での解決
クラス cfoo をテンプレート化するのはこれだけなのですが、問題は元からあるテンプレートメンバ関数の特殊化です
template<typename char_type> class cfoo { std::basic_string<char_type,std::char_traits<char_type>,std::allocator<char_type> > m_text; public: void append( const char_type* type, const char_type* name ){ m_text.append( type ); m_text.push_back( tchar_cast(char_type,'\t')); m_text.append( name ); m_text.append( tstring_cast(char_type,";\n")); } template<typename value_type> void append( const char_type* name ); cfoo():m_text(){} };
はじめにも言った通り、メンバ関数だけを特殊化しようとしても文法違反で特殊化できません
// ダメ template<typename char_type> template<> void cfoo<char_type>::append<int>( const char_type* name ){ append( tstring_cast(char_type,"int"), name ); } template<typename char_type> template<> void cfoo<char_type>::append<float>( const char_type* name ){ append( tstring_cast(char_type,"float"), name ); } // ダメ template<> template<typename char_type> void cfoo<char_type>::append<int>( const char_type* name ){ append( tstring_cast(char_type,"int"), name ); } template<> template<typename char_type> void cfoo<char_type>::append<float>( const char_type* name ){ append( tstring_cast(char_type,"float"), name ); } // ダメ template<typename char_type> void cfoo<char_type>::append<int>( const char_type* name ){ append( tstring_cast(char_type,"int"), name ); } template<typename char_type> void cfoo<char_type>::append<float>( const char_type* name ){ append( tstring_cast(char_type,"float"), name ); }
テンプレートクラスのテンプレート関数を特殊化したい場合はテンプレートクラスも特殊化します
// OK だが template<> template<> void cfoo<char>::append<int>( const char* name ){ append( "int", name ); } template<> template<> void cfoo<char>::append<float>( const char* name ){ append( "float", name ); } template<> template<> void cfoo<wchar_t>::append<int>( const wchar_t* name ){ append( L"int", name ); } template<> template<> void cfoo<wchar_t>::append<float>( const wchar_t* name ){ append( L"float", name ); }
ただ、これでは同じ実装を複数書くことになってしまって面倒です
特殊化部分を別のテンプレートに追い出す
一つのテンプレートで解決できない問題も、そこを別のテンプレートに追い出すと簡単に解決できる場合があります
ここでは次のような補助用のテンプレートクラスを用意してみましょう
template<typename char_type,typename value_type> struct cfoo_append_helper;
特殊化したかったメンバ関数ではこのテンプレートクラスを使うだけにして、特殊化はこのテンプレートクラスに任せてしまいましょう
template<typename char_type> class cfoo { std::basic_string<char_type,std::char_traits<char_type>,std::allocator<char_type> > m_text; public: void append( const char_type* type, const char_type* name ){ m_text.append( type ); m_text.push_back( tchar_cast(char_type,'\t')); m_text.append( name ); m_text.append( tstring_cast(char_type,";\n")); } template<typename value_type> void append( const char_type* name ){ cfoo_append_helper<char_type,value_type>::append( *this, name ); } cfoo():m_text(){} }; template<typename char_type> struct cfoo_append_helper<char_type,int> { static void append( cfoo<char_type>& foo, const char_type* name ){ foo.append( tstring_cast(char_type,"int"), name ); } }; template<typename char_type> struct cfoo_append_helper<char_type,float> { static void append( cfoo<char_type>& foo, const char_type* name ){ foo.append( tstring_cast(char_type,"float"), name ); } };
こうすれば見通しもよくなり、理解しやすくなります
テンプレートの実装で困ったら、頭を切り替えて問題を別のテンプレートに追い出すという方向に考えてみると、このように簡単に解決できるかもしれません
別の追い出し方
この例では、指定された型を文字列化したいというだけのことなので、実は次のようにするのが一番単純かもしれません
template<typename char_type, typename value_type> struct cfoo_typename { static const char_type* const value; }; template<> const char* const cfoo_typename<char,int>::value = "int"; template<> const wchar_t* const cfoo_typename<wchar_t,int>::value = L"int"; template<> const char* const cfoo_typename<char,float>::value = "float"; template<> const wchar_t* const cfoo_typename<wchar_t,float>::value = L"float"; template<typename char_type> class cfoo { std::basic_string<char_type,std::char_traits<char_type>,std::allocator<char_type> > m_text; public: void append( const char_type* type, const char_type* name ){ m_text.append( type ); m_text.push_back( tchar_cast(char_type,'\t')); m_text.append( name ); m_text.append( tstring_cast(char_type,";\n")); } template<typename value_type> void append( const char_type* name ){ append( cfoo_typename<char_type,value_type>::value, name ); } cfoo():m_text(){} };
enable_if を使った実装例
C++11 では、テンプレート関数を特殊化する代わりに std::enable_if を使って次のようにも書くこともできますが、この例では利点はなさそうですね
template<typename char_type> class cfoo { std::basic_string<char_type,std::char_traits<char_type>,std::allocator<char_type> > m_text; public: void append( const char_type* type, const char_type* name ){ m_text.append( type ); m_text.push_back( tchar_cast(char_type,'\t')); m_text.append( name ); m_text.append( tstring_cast(char_type,";\n")); } //template<typename value_type> void append( const char_type* name ); // * = を *= にしてはいけない template<typename value_type, typename std::enable_if<std::is_same<value_type,int>::value>::type* =nullptr> void append( const char_type* name ){ append( tstring_cast(char_type,"int"), name ); } template<typename value_type, typename std::enable_if<std::is_same<value_type,float>::value>::type* =nullptr> void append( const char_type* name ){ append( tstring_cast(char_type,"float"), name ); } cfoo():m_text(){} };