1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

可変引数テンプレートのパラメータパックを任意の場所で分解して std::tuple にしたい

Last updated at Posted at 2021-03-05

ことの発端

Cでよくある非同期関数のコールバック関数でラムダ導入子有りのラムダ式を使えるようにしたい(続き)」の振り返りでこんな課題が残っていたのでした。

パラメータパックの最後の2つを差し替えたいんだけど方法が思いつかなかったので prepare の型が不定になってしまう。

という事で、templateに渡ってくるパラメータパックを分解する方法を考えようと思いました。

方針としては

  • 最終的にパラメータパックはstd::tupleで取り扱えば、std::applyで関数を呼び出せるので、分解したものはstd::tupleで良いかな。
  • std::tuplestd::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 追記)

神様に教えていただいた再帰テンプレートをやってみようと思ったら、躓きがあったので別記事にしました。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?