1
1

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.

パラメータパックをパラメータパックのまま取り扱う何か

Last updated at Posted at 2021-03-10

ことの発端

パラメータパックを取り扱う時、std::tupleは便利なのですが、一度std::tupleになってしまうと、パラメータパックに再展開することができない(できるの?値はできるようになるっぽいけど、展開したいのは「型」です)のが難点だな~という感じ。更に、値があれば合成できるけど、型だけじゃ合成できないとか、そもそも分離できないとか。。。

特に型の操作をしないで、パラメータパックをそのまま取り扱うのであれば、std::applyが使える(C++17以降)ので関数を呼び出す際にはstd::tupleでいいじゃんってなりますが。。。
しかし、パラメータパックの特定のレンジ(例えば2番目から2個とか)を引数にする関数を生成する template を考えると結構パズル状態だったので、その辺整理するために作ってみた感じです。

ただ、なんせ初学者なもので、もしかすると「車輪の再発明」をしているかもしれないけど、ライブラリの使い方を覚える前に、「メタプログラミング作るのって楽しい~」がモチベーションです。(苦笑)
とはいえ、「車輪の再発明品」を業務アプリ内で使う気はないので、STL でこの手の機能を見つけたら、自分のコードは置き換える予定です。:P

parampack_util

今回作ったのは parampack_util です。
この子は、パラメータパックのユーティリティ的なテンプレートになり、パラメータパックを抱えます。
実際には可変引数テンプレート内でparampack_utilを定義して、ゴニョゴニョする感じになるかと思いますが、テストは main() に直書きです。(非templateな世界(?)で使っても何の役にも立たんとです)

template <typename ...T> struct parampack_util;

1. パラメータパックの個数を調べる

static_assert とかで、パラメータの整合性チェックとかでは活躍しそう。
std::tuple_size<std::tuple<T..>>::valueで置き換え可能ですが、練習を兼ねて単機能なものを作った感じです。

using ppu = parampack_util<T0, T1, T2>;
constexpr auto len = ppu::length; /* len = 3 */

投稿直後、sizeof...(T) で一撃と知ったので、この子は日の目をみることがない可哀想な子です。。。ごめんね。。。

車輪の再発明
/* パラメータパックの個数を数える count の実装 */
template <std::size_t I, typename ...ARGS> struct count_impl;
template <std::size_t I, typename T0, typename ...Tx>
  struct count_impl<I, T0, Tx...> :
    count_impl<I + 1, Tx...> {};
template <std::size_t I>
  struct count_impl<I> { static constexpr std::size_t value = I; };

2. パラメータパックのN番目の型を取得する

メタプログラミングやっていて、特定の N番目の型を取得するケースはそうそうない気がするけど、練習がてら作ってみた感じ。もしかすると、パラメータパックの先頭とか最後尾のパラメータの型に制約を設けたい場合に使えるかも?
まぁ、これもstd::tuple_element<0, std::tuple<T...>>::typeで可能ですが、length と同様で、練習を兼ねて単機能なものを作った感じです。

using ppu = parampack_util<int, double, float>;
using t0= ppu::get<0>; /* type = int*/
using t1= ppu::get<1>; /* type = double */
using t2= ppu::get<2>; /* type = float */

3. パラメータパックをパラメータパックのまま転送する

特に作るまでもない機能ですが、この後の range_forward と対比する目的で作りました。

template <typename ...T> struct test {};
using ppu = parampack_util<int, double, float>;
using t = ppu::forward<test>; /* t = test<int, double, float> */

4. パラメータパックのN番目からM個をパラメータパックとして、任意の可変引数テンプレートに転送する

この機能を作りたくて遊んでいた感じです。(笑)

template <typename ...T> struct test {};
using ppu = parampack_util<int, double, float, int*, void*>;
using t = ppu::range_forward<2, 2, test>; /* t = test<float, int*> */

作ってみたものは、こんなんです

# include <type_traits>

/* N番目の型を取得する pick の実装 */
template <std::size_t N, std::size_t I, typename ...ARGS> struct pick_impl;
template <std::size_t N, std::size_t I, typename T0, typename T1, typename ...Tx>
  struct pick_impl<N, I, T0, T1, Tx...> :
    std::conditional<
      N == I,
      pick_impl<N, I + 1, T0>,
      pick_impl<N, I + 1, T1, Tx...>
    >::type {};
template <std::size_t N, std::size_t I, typename T>
  struct pick_impl<N, I, T>{ using type = T; };


/* 型を抱える bundle 君 */
template <typename ...T> struct bundle;

/* パラメータパックの指定した範囲をパラメータパックとして
 * 他の可変引数テンプレートに転送する range_forward の実装
 */
template <std::size_t START, std::size_t END, std::size_t I, template<typename ...> class TARGET, typename ...T>
  struct range_forward_impl;
template <std::size_t START, std::size_t END, std::size_t I, template<typename ...> class TARGET, typename ...TARGS, typename T0, typename ...Tx>
  struct range_forward_impl<START, END, I, TARGET, bundle<TARGS...>, T0, Tx...> :
    std::conditional<
      (START <= I) && (I <= END),
      range_forward_impl<START, END, I + 1, TARGET, bundle<TARGS..., T0>, Tx...>,
      range_forward_impl<START, END, I + 1, TARGET, bundle<TARGS...>, Tx...>
    >::type {};
template <std::size_t START, std::size_t END, std::size_t I, template<typename ...> class TARGET, typename ...TARGS>
  struct range_forward_impl<START, END, I, TARGET, bundle<TARGS...>>
{
  using type = TARGET<TARGS...>;
};

/* パラメータパック操作の寄せ集め parampack_util */
template <typename ...T> struct parampack_util {
  static constexpr std::size_t length = sizeof...(T);

  template <std::size_t N>
    struct get_s
  {
    static_assert(N >= 0);
    static_assert(N < length);
    using type = typename pick_impl<N, 0, T...>::type;
  };
  
  template <std::size_t N> using get = typename get_s<N>::type;

  template <template<typename ...> class TARGET> using forward = TARGET<T...>;

  template <std::size_t N, std::size_t C, template<typename ...> class TARGET>
    struct range_forward_s
  {
    static_assert(N >= 0);
    static_assert(C > 0);
    static_assert(N + C <= length);
    using type = typename range_forward_impl<N, N + C - 1, 0, TARGET, bundle<>, T...>::type;
  };

  template <std::size_t N, std::size_t C, template<typename ...> class TARGET>
    using range_forward = typename range_forward_s<N, C, TARGET>::type;
};

/* test はパラメータパックの転送先 */
template <typename ...T> struct test {
  void operator() (T...) {}; /* 的な感じにしたり。。。 */
};

int main(int, char**) {
  using T0 = int;
  using T1 = float;
  using T2 = double;
  using T3 = void*;

  {
    using ppu = parampack_util<T0>;
    static_assert(ppu::length == 1);
    static_assert(std::is_same<ppu::get<0>, T0>::value);
    static_assert(std::is_same<ppu::forward<test>, test<T0>>::value);
    static_assert(std::is_same<ppu::range_forward<0, 1, test>, test<T0>>::value);
    ppu::range_forward<0, 1, test>()(T0());
  }
  {
    using ppu = parampack_util<T0, T1>;
    static_assert(ppu::length == 2);
    static_assert(std::is_same<ppu::get<0>, T0>::value);
    static_assert(std::is_same<ppu::get<1>, T1>::value);
    static_assert(std::is_same<ppu::forward<test>, test<T0, T1>>::value);
    static_assert(std::is_same<ppu::range_forward<0, 1, test>, test<T0>>::value);
    static_assert(std::is_same<ppu::range_forward<0, 2, test>, test<T0, T1>>::value);
    static_assert(std::is_same<ppu::range_forward<1, 1, test>, test<T1>>::value);
    ppu::range_forward<0, 1, test>()(T0());
    ppu::range_forward<0, 2, test>()(T0(), T1());
    ppu::range_forward<1, 1, test>()(T1());
  }
  {
    using ppu = parampack_util<T0, T1, T2>;
    static_assert(ppu::length == 3);
    static_assert(std::is_same<ppu::get<0>, T0>::value);
    static_assert(std::is_same<ppu::get<1>, T1>::value);
    static_assert(std::is_same<ppu::get<2>, T2>::value);
    static_assert(std::is_same<ppu::forward<test>, test<T0, T1, T2>>::value);
    static_assert(std::is_same<ppu::range_forward<0, 1, test>, test<T0>>::value);
    static_assert(std::is_same<ppu::range_forward<0, 2, test>, test<T0, T1>>::value);
    static_assert(std::is_same<ppu::range_forward<0, 3, test>, test<T0, T1, T2>>::value);
    static_assert(std::is_same<ppu::range_forward<1, 1, test>, test<T1>>::value);
    static_assert(std::is_same<ppu::range_forward<1, 2, test>, test<T1, T2>>::value);
    static_assert(std::is_same<ppu::range_forward<2, 1, test>, test<T2>>::value);
    ppu::range_forward<0, 1, test>()(T0());
    ppu::range_forward<0, 2, test>()(T0(), T1());
    ppu::range_forward<0, 3, test>()(T0(), T1(), T2());
    ppu::range_forward<1, 1, test>()(T1());
    ppu::range_forward<1, 2, test>()(T1(), T2());
    ppu::range_forward<2, 1, test>()(T2());
  }
  {
    using ppu = parampack_util<T0, T1, T2, T3>;
    static_assert(ppu::length == 4);
    static_assert(std::is_same<ppu::get<0>, T0>::value);
    static_assert(std::is_same<ppu::get<1>, T1>::value);
    static_assert(std::is_same<ppu::get<2>, T2>::value);
    static_assert(std::is_same<ppu::get<3>, T3>::value);
    static_assert(std::is_same<ppu::forward<test>, test<T0, T1, T2, T3>>::value);
    static_assert(std::is_same<ppu::range_forward<0, 1, test>, test<T0>>::value);
    static_assert(std::is_same<ppu::range_forward<0, 2, test>, test<T0, T1>>::value);
    static_assert(std::is_same<ppu::range_forward<0, 3, test>, test<T0, T1, T2>>::value);
    static_assert(std::is_same<ppu::range_forward<0, 4, test>, test<T0, T1, T2, T3>>::value);
    static_assert(std::is_same<ppu::range_forward<1, 1, test>, test<T1>>::value);
    static_assert(std::is_same<ppu::range_forward<1, 2, test>, test<T1, T2>>::value);
    static_assert(std::is_same<ppu::range_forward<1, 3, test>, test<T1, T2, T3>>::value);
    static_assert(std::is_same<ppu::range_forward<2, 1, test>, test<T2>>::value);
    static_assert(std::is_same<ppu::range_forward<2, 2, test>, test<T2, T3>>::value);
    static_assert(std::is_same<ppu::range_forward<3, 1, test>, test<T3>>::value);
    ppu::range_forward<0, 1, test>()(T0());
    ppu::range_forward<0, 2, test>()(T0(), T1());
    ppu::range_forward<0, 3, test>()(T0(), T1(), T2());
    ppu::range_forward<0, 4, test>()(T0(), T1(), T2(), T3());
    ppu::range_forward<1, 1, test>()(T1());
    ppu::range_forward<1, 2, test>()(T1(), T2());
    ppu::range_forward<1, 3, test>()(T1(), T2(), T3());
    ppu::range_forward<2, 1, test>()(T2());
    ppu::range_forward<2, 2, test>()(T2(), T3());
    ppu::range_forward<3, 1, test>()(T3());
  }
}

次は std::initializer_list あたりで遊んでみようかと。
(普段お世話になっているけど、実はよく分かっていないんですよね)

書き終わって。。。

投稿して自分の記事を表示すると関連記事とかが下に出るのですが、この投稿にこちらの記事が表示されていまして、早速拝読させていただいて。。。

この記事でsizeof... の存在知った。。。恥ずかしい。。。(自爆)
昔、6502で減算命令の存在知らずにビット反転して加算してたら上司から「減算命令あるよ」と言われたのを思い出す。。。(苦笑)
なんか、記事読んでると、世の中には凄い人がいっぱいいるわ~って感じです:dizzy_face:

1
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?