C++ テンプレート引数に応じた場合分け

テンプレート内でのマルチバイト文字とワイド文字の扱いを例に、テンプレート引数に応じた場合分けを実現する方法について説明します
テンプレート引数に応じて何か処理を分けたいというときには、その場合分けを別のテンプレートに追い出すことで実現できます

テンプレート引数に応じた場合分け

今、Fig.1 のような関数を考えてみましょう
Fig.1

#define	elementsizeof( a )	sizeof((a)[0])
#define	elementsof( a )	(sizeof((a))/elemetsizeof((a)))

void	debug_dump_vectorA( const std::vector<std::string>&	v, int	indent = 0 ){
	int	n = 0;
	for( std::vector<std::string>::const_iterator	p = v.begin(); p != v.end(); ++p ){
		char	s[100];
		snprintf( s, elementsof( s ), "%*c[%d] ", indent, ' ', n );
		OutputDebugStringA( s );
		OutputDebugStringA( *p );
		OutputDebugStringA( "\n" );
		++n;
	}
}

この関数は std::string 専用になっているので、これを std::wstring にも対応したいとします
このようなときにどうしたらいいでしょうか
ここで Fig.2 のように std::wstring 専用のものを別にもう一つ書いても別に構わないのですが
Fig.2

void	debug_dump_vectorW( const std::vector<std::wstring>&	v, int	indent = 0 ){
	int	n = 0;
	for( std::vector<std::wstring>::const_iterator	p = v.begin(); p != v.end(); ++p ){
		WCHAR	s[100];
		wsprintf( s, elementsof( s ), L"%*c[%d] ", indent, L' ', n );
		OutputDebugStringW( s );
		OutputDebugStringW( *p );
		OutputDebugStringW( L"\n" );
		++n;
	}
}
#if   !defined( UNICODE )
#define   debug_dump_vector	debug_dump_vectorA
#else // UNICODE
#define   debug_dump_vector	debug_dump_vectorW
#endif // UNICODE

まったく同じものをもう一つ書くのもばかばかしいので普通はテンプレートにしようとするでしょう
Fig.3

template<class value_type> void	debug_dump_vector( const std::vector<std::basic_string<value_type> >&	v, int	indent = 0 ){
	int	n = 0;
	for( std::vector<std::basic_string<value_type> >::const_iterator	p = v.begin(); p != v.end(); ++p ){
		value_type	s[100];
		_stprintf( s, _T("%*c[%d] "), indent, _T(' '), n );
		OutputDebugString( s );
		OutputDebugString( *p );
		OutputDebugString( _T("\n"));
		++n;
	}
}

ただ、Fig.3 はこのままではコンパイルできません
だからといって、なんだじゃあ Fig.2 にするしかないじゃん。というわけでもありませんよ
がんばってテンプレートにしてみましょう

まず sprintf と OutputDebugString にはマルチバイト用とワイドキャラクタ用の2種類がありますが、それを明示しなかったときにこのどちらが呼ばれるかはコンパイルオプション(プリプロセッサシンボルの UNICODE が定義されているかどうか)によって決まっています
ですからこれはテンプレート引数の指定とは関係ありません
それでは困るのでこれをテンプレート引数の指定によって呼び分けられるようにしましょう
それにはこの2つの関数を外に追い出してそれぞれテンプレート関数とします
Fig.4

template<class T> inline void	OutputDebugString( const std::basic_string<T>&	s ){
	return	OutputDebugStringA( &s[0] );
}
template<> inline void	OutputDebugString<WCHAR>( const std::basic_string<WCHAR>&	s ){
	return	OutputDebugStringW( &s[0] );
}
template<class T> inline int	snprintf( T*	s, SIZE_T	cch, const T*	format,... ){
	va_list	ap;
	va_start( ap, format );
	int	result = vsnprintf( s, cch, format, ap );
	va_end( ap );
	return	result;
}
template<> inline int	snprintf<WCHAR>( WCHAR*	s, SIZE_T	cch, const WCHAR*	format,... ){
	va_list	ap;
	va_start( ap, format );
	int	result = vswprintf( s, cch, format, ap );
	va_end( ap );
	return	result;
}
template<class T> void	debug_dump_vector( const std::vector<std::basic_string<T> >&	v, int	indent = 0 ){
	int	n = 0;
	for( std::vector<std::basic_string<T> >::const_iterator	p = v.begin(); p != v.end(); ++p ){
		T	s[100];
		snprintf<T>( s, elementsof( s ), _T("%*c[%d] "), indent, _T(' '), n );
		OutputDebugString<T>( s );
		OutputDebugString<T>( *p );
		OutputDebugString<T>( _T("\n"));
		++n;
	}
}

こうすることでテンプレート引数の指定によってマルチバイト用とワイドキャラクタ用の関数を呼び分けられるようになりました
しかし Fig.4 のようにしてもまだコンパイルは通りませんね

残った問題は Fig.4 の debug_dump_vector の中で使っている文字列リテラルと文字リテラルです
Fig.4 の debug_dump_vector の中で使っている文字列リテラルと文字リテラルには _T() マクロを適用していますが、この _T() マクロもマルチバイト用とワイドキャラクタ用との切り替えはコンパイルオプションの指定によるので、テンプレート引数の指定とは関係ありません
ですからこれもテンプレート引数の指定に応じてマルチバイト用とワイドキャラクタ用とを使い分けられるようにする必要があります
sprintf と OutputDebugString を外に追い出したのと同じように _T() マクロも外に追い出してみましょう
Fig.5

template<class T> inline const T*	tstring_cast( const CHAR*	s,const WCHAR* ){
	return	s;
}
template<> inline const WCHAR*	tstring_cast<WCHAR>( const CHAR*, const WCHAR*	ws ){
	return	ws;
}
template<class T> inline T	tchar_cast( CHAR	c, WCHAR ){
	return	c;
}
template<> inline WCHAR	tchar_cast<WCHAR>( CHAR, WCHAR	wc ){
	return	wc;
}
template<class T> void	debug_dump_vector( const std::vector<std::basic_string<T> >&	v, int	indent = 0 ){
	int	n = 0;
	for( std::vector<std::basic_string<T> >::const_iterator	p = v.begin(); p != v.end(); ++p ){
		T	s[100];
		snprintf<T>( s, elementsof( s ), tstring_cast<T>("%*c[%d] ",L"%*c[%d] "), indent, tchar_cast<T>(' ',L' '), n );
		OutputDebugString<T>( s );
		OutputDebugString<T>( *p );
		OutputDebugString<T>( tstring_cast<T>("\n",L"\n"));
		++n;
	}
}

このようにすればマルチバイト用の文字列リテラルとワイドキャラクタ用の文字列リテラルをテンプレート引数の指定に応じて使い分けることができます

ただ、毎回 Fig.5 のように書くのでは面倒ですし間違いの元なので、下記のようなマクロを用意してもいいかもしれません
Fig.6

#define	tstring_cast_( T, s )	tstring_cast<T>(s,L##s)
#define	tchar_cast_( T, c )	tchar_cast<T>(c,L##c)
template<class T> void	debug_dump_vector( const std::vector<std::basic_string<T> >&	v, int	indent = 0 ){
	int	n = 0;
	for( std::vector<std::basic_string<T> >::const_iterator	p = v.begin(); p != v.end(); ++p ){
		T	s[100];
		snprintf<T>( s, elementsof( s ), tstring_cast_(T,"%*c[%d] "), indent, tchar_cast_(T,' '), n );
		OutputDebugString<T>( s );
		OutputDebugString<T>( *p );
		OutputDebugString<T>( tstring_cast_(T,"\n")));
		++n;
	}
}

ここまでテンプレートにして最後にマクロが出てくるのは残念ですが、ともかくこれでようやくコンパイルできるようになりました

関数は呼び出しは解決が実行時になってしまうので、コンパイル時に解決したいということであれば、あるいはこのようにしてもいいでしょう

template<typename char_type,char c,wchar_t> struct tchar_cast {
	static const char_type	value = c;
};
template<char c,wchar_t wc> struct tchar_cast<wchar_t,c,wc> {
	static const wchar_t	value = wc;
};
#define	tchar_cast_(char_type,c)	tchar_cast<char_type,c,L##c>::value

このようにテンプレートの中でマルチバイト文字とワイドキャラクタとを使い分けるには、それらをさらに別のテンプレートに追い出してあげることで対応できました

実はこのような仕組みは STL でも使われています
例えば std::char_traits<> は正にこのような目的のためにあるものです
std::char_traits<> はテンプレート引数に応じて strcpy や strlen, strcmp などと wcscpy, wcslen, wcscmp などとを呼び分けられるように用意されているものです

これは何もマルチバイト文字とワイドキャラクタに限った話ではありません
テンプレート引数に応じて何か処理を分けたいというときにはその場合分けを別のテンプレートに追い出すことで実現することができます

また別の例をみてみましょう
下記のように「テンプレート引数に指定された型と同じ型の signed/unsigned な型を明示して使いたい」というようなときにはどうしたらいいでしょうか

template<typename value_type> struct CFoo {
	union {
		value_type	m_buffer[256];
		struct {
			unsigned value_type	m_length;
			value_type	m_data[255];
		};
	};
.
.
.

このような宣言はできないので、ここにテンプレート引数 value_type に応じて適切な unsigned型になるような何らかの仕組みが必要です

unsigned value_type	m_length;

テンプレート引数に応じた場合分けなのですから、ここもテンプレートの外に追い出して別のテンプレートにします

template<typename _plain_type, typename _signed_type = _plain_type,typename _unsigned_type = _plain_type> struct signed_unsigned {
	typedef _plain_type		plain_type;
	typedef _signed_type	signed_type;
	typedef _unsigned_type	unsigned_type;
};

// char だけは特別で char と signed char と unsigned char は文法上明確に区別されるので3つ
template<> struct signed_unsigned<char> : public signed_unsigned<char,signed char,unsigned char> {};
template<> struct signed_unsigned<signed char> : public signed_unsigned<char,signed char,unsigned char> {};
template<> struct signed_unsigned<unsigned char> : public signed_unsigned<char,signed char,unsigned char> {};

// 他の(int と signed int など)は区別されないので2つだけ
template<> struct signed_unsigned<short> : public signed_unsigned<short,signed short,unsigned short> {};
template<> struct signed_unsigned<unsigned short> : public signed_unsigned<short,signed short,unsigned short> {};

template<> struct signed_unsigned<int> : public signed_unsigned<int,signed int,unsigned int> {};
template<> struct signed_unsigned<unsigned int> : public signed_unsigned<int,signed int,unsigned int> {};

template<> struct signed_unsigned<long> : public signed_unsigned<long,signed long,unsigned long> {};
template<> struct signed_unsigned<unsigned long> : public signed_unsigned<long,signed long,unsigned long> {};

こうしておいて次のようにすることで value_type がどんな型であっても unsigned な型を指定することができるようになります

typename signed_unsigned<value_type>::unsigned_type	m_length;

テンプレート引数に応じた場合分けをしたければそこをテンプレートの外に追い出して別のテンプレートとする
これを覚えておきましょう

余談

上の例で char だけ3つになっています
int や short などでは signed も unsigned 指定されていない単に int や short などと書いた型は signed が付いている型と同じ型としてみなされます
つまり int と signed int や short と signed short は常に全く同じ型です
signed も unsigned も付いていなければ signed が省略されているものとして扱われます
これに対して char は単に char と書いたときと signed char と signed を明示したものは全く別の型として扱われますので気をつけてください
実際文法上 char と signed char と unsigned char は全く別の型です
コンパイルオプションで char を暗黙的に unsigned として扱うように指定されているような場合も char と unsigned char は全く別の型です