C++ テンプレートクラスをカスタマイズ可能にする方法
はじめに
簡単なテンプレートクラスを実装しながらテンプレートクラスの動作を使う側から指定できるようにする方法を学びましょう
と言っても、テンプレートクラスをカスタマイズ可能にする方法というのは何か特別なものがあるわけではありません
カスタマイズしたい部分を別のテンプレートに追い出すというだけのことです
enum とループ
enum の要素を列挙するときはだいたい次のようにするでしょうか
enum eFOOBAR { eFOOBAR_INVALID = -1, eFOO = 0, eBAR, eBAZ, eQUX, elementsof_eFOOBAR }; const char* const c_eFOOBAR[] = { "eFOO", "eBAR", "eBAZ", "eQUX" }; void print_foobar( int i, eFOOBAR foobar ){ debug_printf( "%s = %d\n", c_eFOOBAR[i], foobar ); } int main(){ for( int i = 0; i < elementsof_eFOOBAR; ++i ){ print_foobar( i, static_cast<eFOOBAR>(eFOO+i)); } return 0; }
単純な連番でない例えばフラグのような enum の場合には次のようにするでしょうか
enum eFOOBAR2 { eFOOBAR2_INVALID = 0, eFOO2 = 0x01, eBAR2 = 0x02, eBAZ2 = 0x04, eQUX2 = 0x08, elementsof_eFOOBAR2 = 4 }; const char* const c_eFOOBAR2[] = { "eFOO2", "eBAR2", "eBAZ2", "eQUX2" }; void print_foobar2( int i, eFOOBAR2 foobar ){ debug_printf( "%s = 0x%02x\n", c_eFOOBAR2[i], foobar ); } int main(){ for( int i = 0; i < elementsof_eFOOBAR2; ++i ){ print_foobar2( i, static_cast<eFOOBAR2>(eFOO2<<i)); } return 0; }
どちらも同じような処理ですが、次の要素を得るのに一方は +i 一方は <<i となっているところだけが違っています
同じような処理。ということはこれはまとめられそうですね
そして条件が eFOOBAR型なのか、eFOOBAR2型なのか、あるいは別の enum型なのか、と型に応じて場合分けをするのですから、これはテンプレートで実現できそうです
enum の要素を順番に列挙できるようなテンプレートクラスを書いてみましょう
テンプレートクラスその1
列挙するものと言えばイテレータですから、ここでは enum に対するイテレータを書いてみましょう
template<typename enum_type, typename integral_type, enum_type min_value, enum_type max_value, enum_type invalid_value> struct enum_iterator : public std::iterator<std::bidirectional_iterator_tag,enum_type> { static enum_iterator begin(){ return enum_iterator( min_value ); } static enum_iterator end(){ return enum_iterator(); } enum_iterator& operator++(){ advance( m_value, 1 ); return *this; } enum_iterator operator++(int){ enum_iterator result( *this ); advance( m_value, 1 ); return result; } enum_iterator& operator--(){ advance( m_value, -1 ); return *this; } enum_iterator operator--(int){ enum_iterator result( *this ); advance( m_value, -1 ); return result; } enum_type operator*() const { return get(); } bool operator==( const enum_iterator& rhs ) const { return ( **this == *rhs ); } bool operator!=( const enum_iterator& rhs ) const { return !( *this == rhs ); } bool operator==( enum_type value ) const { return ( **this == get( static_cast<integral_type>(value))); } bool operator!=( enum_type value ) const { return !( *this == value ); } enum_iterator(): m_value(static_cast<integral_type>(invalid_value)) { } enum_iterator( const enum_iterator& rhs ): m_value(rhs.m_value) { } enum_iterator& operator=( const enum_iterator& rhs ){ if( this != &rhs ){ m_value = rhs.value; } return *this; } enum_iterator( integral_type value ): // implicit m_value(value) { } enum_iterator& operator=( integral_type value ){ m_value = value; return *this; } private: enum_type get() const { return get( m_value ); } static void advance( integral_type& value, integral_type n ){ value += n; } static enum_type get( integral_type value ){ enum_type result = invalid_value; if(( value >= min_value )&&( value <= max_value )){ result = static_cast<enum_type>(value); } return result; } integral_type m_value; }; enum eFOOBAR { eFOOBAR_INVALID = -1, eFOO = 0, eBAR, eBAZ, eQUX }; const char* const c_eFOOBAR[] = { "eFOO", "eBAR", "eBAZ", "eQUX" }; void print_foobar( eFOOBAR foobar ){ debug_printf( "%s = %d\n", c_eFOOBAR[foobar], foobar ); } int main(){ for( enum_iterator<eFOOBAR,int,eFOO,eQUX,eFOOBAR_INVALID> p = enum_iterator<eFOOBAR,int,eFOO,eQUX,eFOOBAR_INVALID>::begin(); p != enum_iterator<eFOOBAR,int,eFOO,eQUX,eFOOBAR_INVALID>::end(); ++p ){ print_foobar( *p ); } return 0; }
これでなんとなくできましたが、いちいちパラメータをたくさん指定するのが間違いの元ですし、なにより面倒ですね
これをどうにかできないでしょうか
テンプレートクラスその2
たくさんあるパラメータはどれも enum型固有の情報でしたから実は enum の型だけ分かっていればいちいち指定する必要のないものでした
「enum型固有の情報」ということは「型がこの enum型だったらパラメータはこれ」と決まっているということです
「型が○○だったら」といったらテンプレートでの条件分岐ですから、つまりその部分を別のテンプレートとして外に追い出すことで実現できます
この場合はテンプレート引数をまとめてテンプレートクラスにしましょう
template<typename enum_type> struct enum_traits {}; #define DECLARE_ENUM_TRAITS( enum_type, integral_type_, min_value_, max_value_, invalid_value_ ) \ template<> struct enum_traits<enum_type> { \ static const enum_type min_value = min_value_; \ static const enum_type max_value = max_value_; \ static const enum_type invalid_value = invalid_value_; \ typedef integral_type_ integral_type; \ } #define DECLARE_FLAG_ENUM_TRAITS( enum_type, integral_type_, min_value_, max_value_, invalid_value_ ) \ template<> struct enum_traits<enum_type> { \ static const enum_type min_value = min_value_; \ static const enum_type max_value = max_value_; \ static const enum_type invalid_value = invalid_value_; \ typedef integral_type_ integral_type; \ } template<typename enum_type> struct enum_iterator : public std::iterator<std::bidirectional_iterator_tag,enum_type> { typedef typename enum_traits<enum_type>::integral_type integral_type; static const enum_type min_value = enum_traits<enum_type>::min_value; static const enum_type max_value = enum_traits<enum_type>::max_value; static const enum_type invalid_value = enum_traits<enum_type>::invalid_value; static enum_iterator begin(){ return enum_iterator( min_value ); } static enum_iterator end(){ return enum_iterator(); } enum_iterator& operator++(){ advance( m_value, 1 ); return *this; } enum_iterator operator++(int){ enum_iterator result( *this ); advance( m_value, 1 ); return result; } enum_iterator& operator--(){ advance( m_value, -1 ); return *this; } enum_iterator operator--(int){ enum_iterator result( *this ); advance( m_value, -1 ); return result; } enum_type operator*() const { return get(); } bool operator==( const enum_iterator& rhs ) const { return ( **this == *rhs ); } bool operator!=( const enum_iterator& rhs ) const { return !( *this == rhs ); } bool operator==( enum_type value ) const { return ( **this == get( static_cast<integral_type>(value))); } bool operator!=( enum_type value ) const { return !( *this == value ); } enum_iterator(): m_value(static_cast<integral_type>(invalid_value)) { } enum_iterator( const enum_iterator& rhs ): m_value(rhs.m_value) { } enum_iterator& operator=( const enum_iterator& rhs ){ if( this != &rhs ){ m_value = rhs.value; } return *this; } enum_iterator( integral_type value ): // implicit m_value(value) { } enum_iterator& operator=( integral_type value ){ m_value = value; return *this; } private: enum_type get() const { return get( m_value ); } static void advance( integral_type& value, integral_type n ){ value += n; } static enum_type get( integral_type value ){ enum_type result = invalid_value; if(( value >= min_value )&&( value <= max_value )){ result = static_cast<enum_type>(value); } return result; } integral_type m_value; }; enum eFOOBAR { eFOOBAR_INVALID = -1, eFOO = 0, eBAR, eBAZ, eQUX }; DECLARE_ENUM_TRAITS(eFOOBAR,int,eFOO,eQUX,eFOOBAR_INVALID); const char* const c_eFOOBAR[] = { "eFOO", "eBAR", "eBAZ", "eQUX" }; void print_foobar( eFOOBAR foobar ){ debug_printf( "%s = %d\n", c_eFOOBAR[foobar], foobar ); } int main(){ for( enum_iterator<eFOOBAR> p = enum_iterator<eFOOBAR>::begin(); p != enum_iterator<eFOOBAR>::end(); ++p ){ print_foobar( *p ); } return 0; }
これでよさそうですがしかし、integral_type に unsignedな型を指定すると operator-- でエラーになっています
これをどうにかしましょう
テンプレートクラスその3
上記のoperator-- でエラーが出るのは advance の第二引数が integral_type になっているためです
integral_type に unsigned な型が指定されると負の値を指定できなくなってしまいます
ですからここは integral_type そのままでなく、その signed な型でないといけないのでした
どうすればいいでしょうか
これも型による条件分岐ですからやはりテンプレートクラスに追い出すことで解決できます
template<typename integral_type_> struct make_signed { typedef integral_type_ type; }; template<> struct make_signed<unsigned char> { typedef signed char type; }; template<> struct make_signed<unsigned short> { typedef int type; }; template<> struct make_signed<unsigned int> { typedef int type; }; template<> struct make_signed<unsigned long> { typedef long type; }; template<> struct make_signed<unsigned long long> { typedef long long type; }; template<typename enum_type> struct enum_traits {}; #define DECLARE_ENUM_TRAITS( enum_type, integral_type_, min_value_, max_value_, invalid_value_ ) \ template<> struct enum_traits<enum_type> { \ static const enum_type min_value = min_value_; \ static const enum_type max_value = max_value_; \ static const enum_type invalid_value = invalid_value_; \ typedef integral_type_ integral_type; \ } #define DECLARE_FLAG_ENUM_TRAITS( enum_type, integral_type_, min_value_, max_value_, invalid_value_ ) \ template<> struct enum_traits<enum_type> { \ static const enum_type min_value = min_value_; \ static const enum_type max_value = max_value_; \ static const enum_type invalid_value = invalid_value_; \ typedef integral_type_ integral_type; \ } template<typename enum_type> struct enum_iterator : public std::iterator<std::bidirectional_iterator_tag,enum_type> { typedef typename enum_traits<enum_type>::integral_type integral_type; typedef typename make_signed<integral_type>::type signed_type; static const enum_type min_value = enum_traits<enum_type>::min_value; static const enum_type max_value = enum_traits<enum_type>::max_value; static const enum_type invalid_value = enum_traits<enum_type>::invalid_value; static enum_iterator begin(){ return enum_iterator( min_value ); } static enum_iterator end(){ return enum_iterator(); } enum_iterator& operator++(){ advance( m_value, 1 ); return *this; } enum_iterator operator++(int){ enum_iterator result( *this ); advance( m_value, 1 ); return result; } enum_iterator& operator--(){ advance( m_value, -1 ); return *this; } enum_iterator operator--(int){ enum_iterator result( *this ); advance( m_value, -1 ); return result; } enum_type operator*() const { return get(); } bool operator==( const enum_iterator& rhs ) const { return ( **this == *rhs ); } bool operator!=( const enum_iterator& rhs ) const { return !( *this == rhs ); } bool operator==( enum_type value ) const { return ( **this == get( static_cast<integral_type>(value))); } bool operator!=( enum_type value ) const { return !( *this == value ); } enum_iterator(): m_value(static_cast<integral_type>(invalid_value)) { } enum_iterator( const enum_iterator& rhs ): m_value(rhs.m_value) { } enum_iterator& operator=( const enum_iterator& rhs ){ if( this != &rhs ){ m_value = rhs.value; } return *this; } enum_iterator( integral_type value ): // implicit m_value(value) { } enum_iterator& operator=( integral_type value ){ m_value = value; return *this; } private: enum_type get() const { return get( m_value ); } static void advance( integral_type& value, signed_type n ){ value += n; } static enum_type get( integral_type value ){ enum_type result = invalid_value; if(( value >= min_value )&&( value <= max_value )){ result = static_cast<enum_type>(value); } return result; } integral_type m_value; }; enum eFOOBAR { eFOOBAR_INVALID = 0xffffffffU, eFOO = 0, eBAR, eBAZ, eQUX }; DECLARE_ENUM_TRAITS(eFOOBAR,unsigned int,eFOO,eQUX,eFOOBAR_INVALID); const char* const c_eFOOBAR[] = { "eFOO", "eBAR", "eBAZ", "eQUX" }; void print_foobar( eFOOBAR foobar ){ debug_printf( "%s = %d\n", c_eFOOBAR[foobar], foobar ); } int main(){ for( enum_iterator<eFOOBAR> p = enum_iterator<eFOOBAR>::begin(); p != enum_iterator<eFOOBAR>::end(); ++p ){ print_foobar( *p ); } return 0; }
なお、インクリメントの処理とデクリメントの処理を一緒にしておく必要もないので、別のメソッドに分けることでも負値の問題を回避することができます
それはさておき
このままでは単純な連番である enum 型は扱えるのですが、フラグのような enum 型は扱えません
指定された enum が単純な連番であるのかフラグ的なものであるのかに応じて処理を分けることはできないでしょうか
テンプレートクラスその4
もちろんできます
型に応じてというのですからこれもまた問題の部分を別のテンプレートクラスとして追い出せばいいのです
この場合問題になっているのは、前後の要素を得るのに足し算引き算ではなくビットシフトにしないといけないというところですから、private メソッドになっている advance を外に追い出せばよさそうです
template<typename integral_type_> struct make_signed { typedef integral_type_ type; }; template<> struct make_signed<unsigned char> { typedef signed char type; }; template<> struct make_signed<unsigned short> { typedef int type; }; template<> struct make_signed<unsigned int> { typedef int type; }; template<> struct make_signed<unsigned long> { typedef long type; }; template<> struct make_signed<unsigned long long> { typedef long long type; }; template<typename enum_type> struct enum_traits {}; #define DECLARE_ENUM_TRAITS( enum_type, integral_type_, min_value_, max_value_, invalid_value_ ) \ template<> struct enum_traits<enum_type> { \ static const enum_type min_value = min_value_; \ static const enum_type max_value = max_value_; \ static const enum_type invalid_value = invalid_value_; \ typedef integral_type_ integral_type; \ typedef enum_iterator_adapter<enum_type> adapter_type; \ } #define DECLARE_FLAG_ENUM_TRAITS( enum_type, integral_type_, min_value_, max_value_, invalid_value_ ) \ template<> struct enum_traits<enum_type> { \ static const enum_type min_value = min_value_; \ static const enum_type max_value = max_value_; \ static const enum_type invalid_value = invalid_value_; \ typedef integral_type_ integral_type; \ typedef enum_iterator_adapter_flag<enum_type> adapter_type; \ } template<typename enum_type> struct enum_iterator_adapter { typedef typename enum_traits<enum_type>::integral_type integral_type; typedef typename make_signed<integral_type>::type signed_type; static const enum_type min_value = enum_traits<enum_type>::min_value; static const enum_type max_value = enum_traits<enum_type>::max_value; static const enum_type invalid_value = enum_traits<enum_type>::invalid_value; static void advance( integral_type& value, signed_type n ){ value += n; } static enum_type get( integral_type value ){ enum_type result = invalid_value; if(( value >= min_value )&&( value <= max_value )){ result = static_cast<enum_type>(value); } return result; } }; template<typename enum_type> struct enum_iterator_adapter_flag { typedef typename enum_traits<enum_type>::integral_type integral_type; typedef typename make_signed<integral_type>::type signed_type; static const enum_type min_value = enum_traits<enum_type>::min_value; static const enum_type max_value = enum_traits<enum_type>::max_value; static const enum_type invalid_value = enum_traits<enum_type>::invalid_value; static void advance( integral_type& value, signed_type n ){ if( n > 0 ){ value <<= n; } else if( n < 0 ){ value >>= -n; } } static enum_type get( integral_type value ){ enum_type result = invalid_value; if(( value >= min_value )&&( value <= max_value )){ result = static_cast<enum_type>(value); } return result; } }; template<typename enum_type> struct enum_iterator : public std::iterator<std::bidirectional_iterator_tag,enum_type> { typedef typename enum_traits<enum_type>::integral_type integral_type; typedef typename enum_traits<enum_type>::adapter_type adapter_type; static const enum_type min_value = enum_traits<enum_type>::min_value; static const enum_type max_value = enum_traits<enum_type>::max_value; static const enum_type invalid_value = enum_traits<enum_type>::invalid_value; static enum_iterator begin(){ return enum_iterator( min_value ); } static enum_iterator end(){ return enum_iterator(); } enum_iterator& operator++(){ adapter_type::advance( m_value, 1 ); return *this; } enum_iterator operator++(int){ enum_iterator result( *this ); adapter_type::advance( m_value, 1 ); return result; } enum_iterator& operator--(){ adapter_type::advance( m_value, -1 ); return *this; } enum_iterator operator--(int){ enum_iterator result( *this ); adapter_type::advance( m_value, -1 ); return result; } enum_type operator*() const { return get(); } bool operator==( const enum_iterator& rhs ) const { return ( **this == *rhs ); } bool operator!=( const enum_iterator& rhs ) const { return !( *this == rhs ); } bool operator==( enum_type value ) const { return ( **this == get( static_cast<integral_type>(value))); } bool operator!=( enum_type value ) const { return !( *this == value ); } enum_iterator(): m_value(static_cast<integral_type>(invalid_value)) { } enum_iterator( const enum_iterator& rhs ): m_value(rhs.m_value) { } enum_iterator& operator=( const enum_iterator& rhs ){ if( this != &rhs ){ m_value = rhs.value; } return *this; } enum_iterator( integral_type value ): // implicit m_value(value) { } enum_iterator& operator=( integral_type value ){ m_value = value; return *this; } private: enum_type get() const { return get( m_value ); } static enum_type get( integral_type value ){ return adapter_type::get( value ); } integral_type m_value; }; enum eFOOBAR { eFOOBAR_INVALID = -1, eFOO = 0, eBAR, eBAZ, eQUX }; DECLARE_ENUM_TRAITS(eFOOBAR,int,eFOO,eQUX,eFOOBAR_INVALID); enum eFOOBAR2 { eFOOBAR2_INVALID = 0, eFOO2 = 0x01, eBAR2 = 0x02, eBAZ2 = 0x04, eQUX2 = 0x08 }; DECLARE_FLAG_ENUM_TRAITS(eFOOBAR2,unsigned int,eFOO2,eQUX2,eFOOBAR2_INVALID); const char* const c_eFOOBAR[] = { "eFOO", "eBAR", "eBAZ", "eQUX" }; const char* const c_eFOOBAR2[] = { "eFOO2", "eBAR2", "eBAZ2", "eQUX2" }; void print_foobar( eFOOBAR foobar ){ debug_printf( "%s = %d\n", c_eFOOBAR[foobar], foobar ); } void print_foobar2( int n, eFOOBAR2 foobar2 ){ debug_printf( "%s = 0x%02x\n", c_eFOOBAR2[n], foobar2 ); } int main(){ for( enum_iterator<eFOOBAR> p = enum_iterator<eFOOBAR>::begin(); p != enum_iterator<eFOOBAR>::end(); ++p ){ print_foobar( *p ); } int n = 0; for( enum_iterator<eFOOBAR2> p = enum_iterator<eFOOBAR2>::begin(); p != enum_iterator<eFOOBAR2>::end(); ++p ){ print_foobar2( n, *p ); ++n; } return 0; }
このようにテンプレートクラスの private メソッドやメンバ変数などを外に追い出すと、そこを外部から指定できるようになり柔軟性が出てきます
まとめ
テンプレートクラスをカスタマイズ可能にする方法というのは何か特別なものがあるわけではありません
- 外から指定できるパラメータが多すぎるようになったらテンプレートクラスにまとめる
- 型に応じて処理を切り替えたかったらその処理をテンプレートクラスに追い出す
とりわけ private メソッドなどは積極的に別のテンプレートクラスに追い出す - 何か条件分岐をしたいところがあったらそこを別のテンプレートに追い出す
つまりカスタマイズ可能にしたい部分を別のテンプレートに追い出すということです
それがテンプレートクラスをカスタマイズ可能にする方法です