ことの発端
「Cでよくある非同期関数のコールバック関数でラムダ導入子有りのラムダ式を使えるようにしたい(続き)」の振り返りでこんな課題が残っていたのでした。
パラメータパックの最後の2つを差し替えたいんだけど方法が思いつかなかったので prepare の型が不定になってしまう。
という事で、templateに渡ってくるパラメータパックを分解する方法を考えようと思いました。
方針としては
- 最終的にパラメータパックは
std::tuple
で取り扱えば、std::apply
で関数を呼び出せるので、分解したものはstd::tuple
で良いかな。 -
std::tuple
はstd::tuple_cat
で合成できるけど、std::tuple
を分離させることが困難な気がする。(調べてみたけど、なかなか情報が見つからない) - となると、パラメータパックの状態で分離するしかなさそうだ。
こんな感じで進めてみました。
書いてみた
# include <iostream>
# include <tuple>
template <int N, typename ...ARGS> struct pick;
template <typename ...ARGS> struct pick<0, ARGS...> {
using types = std::tuple<>;
using after = std::tuple<ARGS...>;
};
template <typename T1, typename ...ARGS> struct pick<1, T1, ARGS...> {
using types = std::tuple<T1>;
using after = std::tuple<ARGS...>;
};
template <typename T1, typename T2, typename ...ARGS> struct pick<2, T1, T2, ARGS...> {
using types = std::tuple<T1, T2>;
using after = std::tuple<ARGS...>;
};
template <typename T1, typename T2, typename T3, typename ...ARGS> struct pick<3, T1, T2, T3, ARGS...> {
using types = std::tuple<T1, T2, T3>;
using after = std::tuple<ARGS...>;
};
template <typename T1, typename T2, typename T3, typename T4, typename ...ARGS> struct pick<4, T1, T2, T3, T4, ARGS...> {
using types = std::tuple<T1, T2, T3, T4>;
using after = std::tuple<ARGS...>;
};
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename ...ARGS> struct pick<5, T1, T2, T3, T4, T5, ARGS...> {
using types = std::tuple<T1, T2, T3, T4, T5>;
using after = std::tuple<ARGS...>;
};
template <int N, typename ...ARGS> struct rpick {
using before = typename pick<std::tuple_size<std::tuple<ARGS...>>::value - N, ARGS...>::types;
using types = typename pick<std::tuple_size<std::tuple<ARGS...>>::value - N, ARGS...>::after;
};
int main(void) {
using pick_type = pick<2, int, float, void*, char>::types;
/* pick_type = std::tuple<int, float> */
using pick_after = pick<2, int, float, void*, char>::after;
/* pick_after = std::tuple<void *, char> */
using rpick_type = rpick<1, int, float, void*, char>::types;
/* rpick_type = std::tuple<char> */
using rpick_before = rpick<1, int, float, void*, char>::before;
/* rpick_type = std::tuple<char> */
}
何というか、「可変引数テンプレート
ってこの手の作業を楽にするためにできた機能じゃないの?」という気もするが、これでいいのか?
使い方
パラメータパックの先頭から N 個をstd::tuple
にする.
pick<N, ARGS...>::types;
パラメータパックの後ろから N 個をstd::tuple
にする.
rpick<N, ARGS...>::types;
そうすると、こんな感じで使えるよになるかと
template <typename ...ARGS> struct sample {
using first = typename pick<1, ARGS...>::types; /* パラメータパックの最初の1個 */
using last = typename rpick<1, ARGS...>::types; /* パラメータパックの最後の1個 */
};
注意点
サンプルコードではパラメータパックは5個までなので、増えてきたら頑張って追加する。
今度はマクロ作ろうか。。。
振り返り
- pickちゅうよりsplitって感じかな?
- というか、boost なんかに便利な「アレ」的なのは無いかな?
マクロ版もやってみた
# include <iostream>
# include <tuple>
template <int N, typename ...ARGS> struct pick;
template <typename ...ARGS> struct pick<0, ARGS...> {
using types = std::tuple<>;
using after = std::tuple<ARGS...>;
};
# define __(N, TN, T) \
template <TN, typename ...ARGS> struct pick<N, T, ARGS...> { \
using types = std::tuple<T>; \
using after = std::tuple<ARGS...>;\
};
# define _(...) __VA_ARGS__
__( 1, _(typename T1), _(T1))
__( 2, _(typename T1, typename T2), _(T1, T2))
__( 3, _(typename T1, typename T2, typename T3), _(T1, T2, T3))
__( 4, _(typename T1, typename T2, typename T3, typename T4), _(T1, T2, T3, T4))
__( 5, _(typename T1, typename T2, typename T3, typename T4, typename T5), _(T1, T2, T3, T4, T5))
__( 6, _(typename T1, typename T2, typename T3, typename T4, typename T5, typename T6), _(T1, T2, T3, T4, T5, T6))
__( 7, _(typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7), _(T1, T2, T3, T4, T5, T6, T7))
__( 8, _(typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8), _(T1, T2, T3, T4, T5, T6, T7, T8))
__( 9, _(typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9), _(T1, T2, T3, T4, T5, T6, T7, T8, T9))
__(10, _(typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9, typename T10), _(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10))
# undef __
# undef _
template <int N, typename ...ARGS> struct rpick {
using before = typename pick<std::tuple_size<std::tuple<ARGS...>>::value - N, ARGS...>::types;
using types = typename pick<std::tuple_size<std::tuple<ARGS...>>::value - N, ARGS...>::after;
};
昔、マクロを多用して魔法のようなプログラムを作っていた人がいたが、メンテナンス性が酷すぎてとても手を入れられない状況になったのがトラウマで、マクロってメリットがある場面もあるけど、反面「罠」を作る場面も多いと思うので、どうにも気が引ける。。。
マクロにスコープ的な考え方が導入されれば良いんだけどなぁ〜(あと push pop 的なのでも良いけど)
というか、こう書いていると、規則性があるし、何となく template
にできんもんかなぁ〜と、考え中。。。
神様のお陰でできました(2021/03/08 追記)
神様に教えていただいた再帰テンプレートをやってみようと思ったら、躓きがあったので別記事にしました。