読者です 読者をやめる 読者になる 読者になる

char 型の可変長パラメータ引数を使ってコンパイル時に文字列を生成する

C++

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 版があると良さそう.

文字列リテラルから文字型の可変長パラメータパックに簡単に落とす方法があれば結構良い感じなんだけれど,プリプロセッサを使わないと無理っぽい(し,プリプロセッサを使っても難しそう).