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