BOOST_PP を使用して、複引数の template 関数を自動生成する
[お題]
template< typename T0, typename T1 > std::string join_string(const T0& arg0, const T1& arg1){ return boost::lexical_cast<std::string>(arg0) + boost::lexical_cast<std::string>(arg1); } template< typename T0, typename T1, typename T2 > std::string join_string(const T0& arg0, const T1& arg1, const T2& arg2){ return boost::lexical_cast<std::string>(arg0) + boost::lexical_cast<std::string>(arg1) + boost::lexical_cast<std::string>(arg2); }
こんな感じで、2個以上の引数を受け取って、string で連結して返す関数を生成してみます。
template 引数の部分を生成
template< typename T0, typename T1, typename T2 >
typename T の後ろに数値を付けて欲しいので、BOOST_PP_ENUM_SHIFTED_PARAMS を使って展開します。
template< BOOST_PP_ENUM_PARAMS(2, typename T) // typename T ## n, ... , typename T ## n-1 >
BOOST_PP_ENUM_PARAMS は、第二引数の語尾に数値を連結して展開します。
関数の引数の部分を生成
join_string(const T0& t0, const T1& t1, const T2& t2)
関数の引数は、型名と引数名の2つに数値を付けるので、BOOST_PP_ENUM_BINARY_PARAMS を使用します。
join_string( BOOST_PP_ENUM_BINARY_PARAMS(2, const T, &arg) ) // const T ## n, &arg ## n, ... , onst T ## n-1, &arg ## n-1
BOOST_PP_ENUM_PARAMS の引数が2つ版です。
& を書く位置に注意。
string の連結部分を生成
return boost::lexical_cast<std::string>(arg0)
+ boost::lexical_cast<std::string>(arg1)
+ boost::lexical_cast<std::string>(arg2);
この部分はちょっと複雑なのでまず、
+ boost::lexical_cast<std::string>(t1)
の部分をマクロで定義します。
#define PLUS_TO_STRING(z, n, name) \ + boost::lexical_cast<std::string>( \ BOOST_PP_CAT(name, n) \ ) PLUS_TO_STRING(_, 1, arg) // + boost::lexical_cast<std::string>(arg1)
arg の後ろに数値を付けるのは、BOOST_PP_CAT を使用。
このマクロを使って、string に変換し、足す部分を展開します。
最終的にはこうなります。
#define PLUS_TO_STRING(z, n, name) \ + boost::lexical_cast<std::string>( \ BOOST_PP_CAT(name, n) \ ) return boost::lexical_cast<std::string>(arg0) BOOST_PP_REPEAT_FROM_TO(1, 3, PLUS_TO_STRING, arg); // PLUS_TO_STRING(z, 1, arg) ... PLUS_TO_STRING(z, n-1, arg)
1からカウントして欲しいので、BOOST_PP_REPEAT_FROM_TO を使って、初期カウントを設定して展開します。
ちなみにBOOST_PP_ENUM_ 等を使って展開させようとするとマクロの後に "," が追加され、コンパイルエラーが出るので注意。
はまりました…。
引数が3つの関数を生成
上記の事を踏まえて、3つの引数を取る関数を作ってみます。
#define PLUS_TO_STRING(z, n, name) \ + boost::lexical_cast<std::string>( \ BOOST_PP_CAT(name, n) \ ) template< BOOST_PP_ENUM_PARAMS(3, typename T) > std::string join_string( BOOST_PP_ENUM_BINARY_PARAMS(3, const T, &arg) ){ return boost::lexical_cast<std::string>(arg0) BOOST_PP_REPEAT_FROM_TO(1, 3, PLUS_TO_STRING, arg); } #undef PLUS_TO_STRING
これで3つの引数を取る関数が展開されます。
2〜N個の引数を取る関数の生成。
関数の定義部分を全てマクロにしてしまい、BOOST_PP_REPEAT で生成させます。
#define PLUS_TO_STRING(z, n, name) \ + boost::lexical_cast<std::string>( \ BOOST_PP_CAT(name, n) \ ) #define JOIN_STRING_FUNC(z, n, d) \ template< \ BOOST_PP_ENUM_PARAMS(n, typename T) \ > \ std::string \ join_string( \ BOOST_PP_ENUM_BINARY_PARAMS(n, const T, &arg) \ ){ \ return boost::lexical_cast<std::string>(arg0) \ BOOST_PP_REPEAT_FROM_TO(1, n, PLUS_TO_STRING, arg); \ } BOOST_PP_REPEAT_FROM_TO(2, 11, JOIN_STRING_FUNC, _) // JOIN_STRING_FUNC(z, 2, _) ... JOIN_STRING_FUNC(z, 11-1, _) #undef JOIN_STRING_FUNC #undef PLUS_TO_STRING
これで必要な引数分の join_string を展開してくれます。
すばらしい。
テストコード
ついでにテスト用のコードも BOOST_PP を使ってみる。
#define JOIN_STRING_DEBUG(z, n, d) \ std::cout << join_string( \ BOOST_PP_ENUM_SHIFTED_PARAMS(BOOST_PP_ADD(n, 1), ) \ ) << std::endl; BOOST_PP_REPEAT_FROM_TO(2, 11, JOIN_STRING_DEBUG, _)
[出力]
12 123 1234 12345 123456 1234567 12345678 123456789 12345678910
以上、終了。
こんな感じで、BOOST_PP を使うとかなり作業量が簡略化されます。
すごい時代になったものだ。
ただし、デバッグが死ぬほど手間なので多様は禁物。
正直、複数の template を展開するぐらいしか使いたくない・・・。
[boost]
ver 1.44.0
[参照]
http://d.hatena.ne.jp/DigitalGhost/20091213/1260732009