meryngii.neta

今日も新たな"ネタ"を求めて。

書式もコンパイル時に解析するprintfもどき

この記事の内容は古くなっており、正式なC++11では動作しません。詳細は中3女子さんからのコメントをば。
C言語の普通のprintfについて知りたい人は、他を当たった方がいい。
C++0xのVariadic Templatesのおかげでprintfもどきを作るのが簡単になった。書式文字列を解析するコードを書けば、簡単にタイプセーフなものを作ることができる。
さて、書式文字列を実行時に解析するサンプルはすでにあるので、今回はコンパイル時に簡易的に解析するものを作った。ただし、ややこしくなるので精度やら文字列幅やらの指定は実装していない。*1あくまでもどき。また、データ型は静的に決まるので書式での型指定(dやsなど)は無視する。*2

// mpl::int_でもいい
template <int x> struct int_ { };

template <char... Chars>
struct char_list { static const size_t size = sizeof...(Chars); };

template <class CharList>
struct char_list_separator;

template <char Car, char... Cdr>
struct char_list_separator<char_list<Car, Cdr...>>
{
	static const char car = Car;
	typedef char_list<Cdr...>	cdr;
};

template <class Chars, class... Types>
inline void print_0(Types... args);

template <class Chars, class Type, class... Types>
inline void print_3(int_<true>, Type arg, Types... args)
{
	std::cout << arg;
	print_0<typename char_list_separator<Chars>::cdr>(args...);
}
template <class Chars, class... Types>
inline void print_3(int_<false>, Types... args)
{
	std::cout << '%';
	print_0<typename char_list_separator<Chars>::cdr>(args...);
}

template <class Chars, class... Types>
inline void print_2(int_<true>, Types... args)
{
	typedef typename char_list_separator<Chars>::cdr	cdr;
	print_3<cdr>(int_<char_list_separator<cdr>::car != '%'>(), args...);
}
template <class Chars, class... Types>
inline void print_2(int_<false>, Types... args)
{
	std::cout << char_list_separator<Chars>::car;
	print_0<typename char_list_separator<Chars>::cdr>(args...);
}

template <class Chars, class... Types>
inline void print_1(int_<true>, Types... args)
{
	print_2<Chars>(int_<char_list_separator<Chars>::car == '%'>(), args...);
}
template <class Chars>
inline void print_1(int_<false>)
{
	// finished
}

template <class Chars, class... Types>
inline void print_0(Types... args)
{
	print_1<Chars>(int_<Chars::size != 0>(), args...);
}

template <char... Chars, class... Types>
inline void print(char_list<Chars...>, Types... args)
{
	print_0<char_list<Chars...>>(args...);
}

そしてこれをこんな風に使う。

print(char_list<'r','=','%','f','%','%','\n','%','s','.'>(),
12.34, "Test");

ConceptGCCでの実行結果

r=12.34%
Test.

これだけでは使い物にならない。ここでUser-Defined Literalsが登場する。

template <char... Chars>
inline char_list<Chars...> operator "" cl()
{
	return char_list<Chars...>();
}

これでこう書けるはずだ。

print("r=%f%%\n%s."cl, 12.34, "Test");

これでやっとそれらしくなったが、変な尾ひれがついているのは気にしないでほしい。ただしこれはまだテストできない。ConceptGCCがUser-Defined Literalsを実装していないからだ。

*1:不可能というわけではない。

*2:コンパイル時に書式での型指定と実際の型を照合することもできるだろう。