char 型の可変長パラメータ引数を使ってコンパイル時に文字列を生成する
C++11 から可変長のテンプレート引数を取ることが出来るようになったので,
template< char... Chars > struct string {};
というクラステンプレートをつくって文字列を可変長パラメータパックに突っ込むことを考えてみた.
例えば,"moudameda"
という文字列は次の型で表現出来る.
string<'m','o','u','d','a','m','e','d','a',>
試しにコンパイル時に FizzBuzz の文字列を生成するコードを書いてみた.
#include <cstddef> #include <array> #include <iostream> template< char... Chars > struct string{ constexpr std::array<char, sizeof...(Chars)+1> to_array() const { return {{Chars..., '\0'}}; } }; template< std::size_t Value, class Acc = string<>, bool = (Value < 10) > struct num2string; template< std::size_t Value, char... Chars > struct num2string< Value, string<Chars...>, false > : num2string< Value / 10, string< Value % 10 + '0', Chars... > >{}; template< std::size_t Value, char... Chars > struct num2string< Value, string<Chars...>, true >{ typedef string< Value + '0', Chars... > type; }; template< class T, class U > struct joint_; template< char... C1, char... C2 > struct joint_< string<C1...>, string<C2...> >{ typedef string<C1..., C2...> type; }; template< class Str1, class Str2 > using joint = typename joint_<Str1, Str2>::type; template<class T> struct addnl_; template< char... Chars > struct addnl_< string<Chars...> >{ typedef string<Chars..., '\n'> type; }; template<class Str> using addnl = typename addnl_<Str>::type; typedef string<'f', 'i', 'z', 'z'> fizz; typedef string<'b', 'u', 'z', 'z'> buzz; template< std::size_t Start, std::size_t Last, class Acc = string<>, std::size_t Mod3 = Start%3, std::size_t Mod5 = Start%5, bool Finish = (Start>=Last) > struct fizzbuzz{ typedef Acc type; }; template< std::size_t Start, std::size_t Last, class Acc > struct fizzbuzz<Start, Last, Acc, 0, 0, false> : fizzbuzz<Start+1, Last, addnl<joint<joint<Acc, fizz>, buzz>> >{}; template< std::size_t Start, std::size_t Last, class Acc, std::size_t Mod5 > struct fizzbuzz<Start, Last, Acc, 0, Mod5, false> : fizzbuzz<Start+1, Last, addnl<joint<Acc, fizz>> >{}; template< std::size_t Start, std::size_t Last, class Acc, std::size_t Mod3 > struct fizzbuzz<Start, Last, Acc, Mod3, 0, false> : fizzbuzz<Start+1, Last, addnl<joint<Acc, buzz>> >{}; template< std::size_t Start, std::size_t Last, class Acc, std::size_t Mod3, std::size_t Mod5 > struct fizzbuzz<Start, Last, Acc, Mod3, Mod5, false> : fizzbuzz<Start+1, Last, addnl<joint<Acc, typename num2string<Start>::type>> >{}; int main() { static_assert(typename fizzbuzz<1, 100>::type().to_array().size() == 409, ""); std::cout << typename fizzbuzz<1, 100>::type().to_array().data(); return 0; }
template aliases を使うと typename ~::type
を省けて,結構すっきりして良い感じだった.
Boost.MPL とかのメタ関数にも alias 版があると良さそう.
文字列リテラルから文字型の可変長パラメータパックに簡単に落とす方法があれば結構良い感じなんだけれど,プリプロセッサを使わないと無理っぽい(し,プリプロセッサを使っても難しそう).