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 メソッドなどは積極的に別のテンプレートクラスに追い出す
  • 何か条件分岐をしたいところがあったらそこを別のテンプレートに追い出す

つまりカスタマイズ可能にしたい部分を別のテンプレートに追い出すということです
それがテンプレートクラスをカスタマイズ可能にする方法です