2
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.

可変引数テンプレートのN番目のパラメータを除いたものをstd::tupleで定義したい

Last updated at Posted at 2021-03-07

ことの発端

先日、こんな記事を投稿したのですが

その振り返りで

ラムダ式にrefcon無くしたいかも

というものを実現しようかという話です。

やりたいこと

  • 可変引数テンプレートのN番目のパラメータを除いたstd::tupleを作りたい。
  • N番目は非型テンプレートパラメータとしたい。
  • std::tiestd::ignore で「値」を除去できるが、欲しいのは除去後の「型」なので今回はスコープとしよう。(declvalでゴニョゴニョできるかも??)

結論、こんな感じにしたい。

using t = omit<1, void*, int>;
/* t = std::tuple<int> */

やってみた

1. まず最初に

いきなりN番目とか作れんので、まず最初の1個を除外するコードを書いてみる。

#include <tuple>
template <typename T1, typename ...ARGS> struct omit_1 {
  using type = std::tuple<ARGS...>;
};
int main(void){
  using t = omit_1<void*, int, int, float>::type;
  /* t = std::tuple<int, int, float> */
}

ふむ、良い感じ。

2. Nを0~3とするとどんな感じになるか試してみる

やはり、ここでもN番目とか作るんじゃなくて、Nが変化するとどんな感じのコードになるかをやってみるのが良いと思うわけで。。。

#include <tuple>
template <typename ...ARGS> struct omit_0 {
  using type = std::tuple<ARGS...>;
};

template <typename T1, typename ...ARGS> struct omit_1 {
  using type = std::tuple<ARGS...>;
};

template <typename T1, typename T2, typename ...ARGS> struct omit_2 {
  using type = std::tuple<T1, ARGS...>;
};

template <typename T1, typename T2, typename T3, typename ...ARGS> struct omit_3 {
  using type = std::tuple<T1, T2, ARGS...>;
};

int main(void){
  using t0 = omit_0<void*, int, int, float>::type;
  /* t0 = std::tuple<void *, int, int, float> */

  using t1 = omit_1<void*, int, int, float>::type;
  /* t1 = std::tuple<int, int, float> */

  using t2 = omit_2<void*, int, int, float>::type;
  /* t2 = std::tuple<void *, int, float> */

  using t3 = omit_3<void*, int, int, float>::type;
  /* t3 = std::tuple<void *, int, float> */
}

ちゃんと、動作確認もしつつ。。。
で、こうすると徐々にパターンが見えてくるので。。。

3. 特殊化使ってN番目を表現してみる

私のような凡人には、命題を一般化することが難しいので、せめてNを非型パラメータにできないかってことで、omitという1次テンプレートを宣言して、omit の特殊化を定義していく方針にする。
当然、N個定義しなきゃならんけど、関数の引数で10個超えるなんてなかなか拝めるものじゃないので、10個くらい用意しとけば良いんじゃないかしら?(そこまで引数が増えたら構造体を渡すのが一般的じゃないかなぁ)

という、ことでまずは 0 ~ 3 を特殊化定義していみる。

template <int N, typename ...ARGS> struct omit;
template <int N, typename ...ARGS> struct omit<0, ARGS...> : public omit_0<ARGS...> {};
template <int N, typename ...ARGS> struct omit<1, ARGS...> : public omit_1<ARGS...> {};
template <int N, typename ...ARGS> struct omit<2, ARGS...> : public omit_2<ARGS...> {};
template <int N, typename ...ARGS> struct omit<3, ARGS...> : public omit_3<ARGS...> {};

4. エラーを潰す

すると。。。

error: template parameters not deducible in partial specialization:
[build]  template <int N, typename ...ARGS> struct omit<0, ARGS...> : public omit_0<ARGS...> {};
[build]                                            ^~~~~~~~~~~~~~~~

ふむ。。。
英語力が低いのでgoogle先生に翻訳してもらうと

部分的な特殊化で推論できないテンプレートパラメータ

メタプログラミングに慣れていないと、なかなかミスを見つけられない。
悩むこと数分、「はっ」と気付く。

template <int N, typename ...ARGS> struct omit;
template <typename ...ARGS> struct omit<0, ARGS...> : public omit_0<ARGS...> {};
template <typename ...ARGS> struct omit<1, ARGS...> : public omit_1<ARGS...> {};
template <typename ...ARGS> struct omit<2, ARGS...> : public omit_2<ARGS...> {};
template <typename ...ARGS> struct omit<3, ARGS...> : public omit_3<ARGS...> {};

そりゃそうだと。。。

5. 最後から N番目もやってみる

これで、非型パラメータを使えるようになったので、最後から N番目というのが書けるようになる。

template <int N, typename ...ARGS> struct romit {
  using type = typename omit<std::tuple_size<std::tuple<ARGS...>>::value - N, ARGS...>::type;
};

6. できたものをテストしてみる

テストコードと言っても、型を取得するだけなので、今回は開発環境(VSCode)で定義にカーソル当てて型を確認することにした。

#include <tuple>
template <typename ...ARGS> struct omit_0 {
  using type = std::tuple<ARGS...>;
};

template <typename T1, typename ...ARGS> struct omit_1 {
  using type = std::tuple<ARGS...>;
};

template <typename T1, typename T2, typename ...ARGS> struct omit_2 {
  using type = std::tuple<T1, ARGS...>;
};

template <typename T1, typename T2, typename T3, typename ...ARGS> struct omit_3 {
  using type = std::tuple<T1, T2, ARGS...>;
};

template <int N, typename ...ARGS> struct omit;
template <typename ...ARGS> struct omit<0, ARGS...> : public omit_0<ARGS...> {};
template <typename ...ARGS> struct omit<1, ARGS...> : public omit_1<ARGS...> {};
template <typename ...ARGS> struct omit<2, ARGS...> : public omit_2<ARGS...> {};
template <typename ...ARGS> struct omit<3, ARGS...> : public omit_3<ARGS...> {};

template <int N, typename ...ARGS> struct romit {
  using type = typename omit<std::tuple_size<std::tuple<ARGS...>>::value - N, ARGS...>::type;
};

int main(void){
  using t0 = omit<0, void*, int, int*, float>::type;
  /* t0 = std::tuple<void*, int, int*, float> */

  using t1 = omit<1, void*, int, int*, float>::type;
  /* t1 = std::tuple<int, int*, float> */

  using t2 = omit<2, void*, int, int*, float>::type;
  /* t2 = std::tuple<void*, int*, float> */

  using t3 = omit<3, void*, int, int*, float>::type;
  /* t3 = std::tuple<void*, int, float> */

  // using t0r = romit<0, void*, int, int*, float>::type;
  /* omit<4, ...> は特殊化されていないのでomitは不完全型(incomplete type)というエラー */

  using t1r = romit<1, void*, int, int*, float>::type;
  /* t1 = std::tuple<void*, int, float> */

  using t2r = romit<2, void*, int, int*, float>::type;
  /* t2 = std::tuple<void*, int*, float> */

  using t3r = romit<3, void*, int, int*, float>::type;
  /* t3 = std::tuple<int, int*, float> */
}

7.N が 0 ~ 10 まで作りましょう

大丈夫そうなので、これを10個作りますか。。。
コピペで大量に作っても良いけど、効率を考えてマクロを作ることにしました。
(諸事情がありマクロを書くことが罪悪感しかないのだけど、堪えることにした)

0 と 1 だけは特殊なので、そのまま定義しちゃって、2 以降をマクロで作る感じにしました。

#include <tuple>

template <int N, typename ...ARGS> struct omit;

template <typename ...ARGS> struct omit_0 {
  using type = std::tuple<ARGS...>;
};
template <typename ...ARGS> struct omit<0, ARGS...> : public omit_0<ARGS...> {};

template <typename T1, typename ...ARGS> struct omit_1 {
  using type = std::tuple<ARGS...>;
};
template <typename ...ARGS> struct omit<1, ARGS...> : public omit_1<ARGS...> {};

#define __(V, TN, N) template <TN, typename ...ARGS> struct omit_##V { \
  using type = std::tuple<N, ARGS...>; \
}; \
template <typename ...ARGS> struct omit<V, ARGS...> : public omit_##V<ARGS...> {};
#define _(...) __VA_ARGS__

__( 2, _(typename T1, typename T2), _(T1))
__( 3, _(typename T1, typename T2, typename T3), _(T1, T2))
__( 4, _(typename T1, typename T2, typename T3, typename T4), _(T1, T2, T3))
__( 5, _(typename T1, typename T2, typename T3, typename T4, typename T5), _(T1, T2, T3, T4))
__( 6, _(typename T1, typename T2, typename T3, typename T4, typename T5, typename T6), _(T1, T2, T3, T4, T5))
__( 7, _(typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7), _(T1, T2, T3, T4, T5, T6))
__( 8, _(typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8), _(T1, T2, T3, T4, T5, T6, T7))
__( 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))
__(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))

#undef _
#undef __

template <int N, typename ...ARGS> struct romit {
  using type = typename omit<std::tuple_size<std::tuple<ARGS...>>::value - N, ARGS...>::type;
};

所感

多分、こんなのSTLやboostを探せば出てくるのかもね~
まぁ、この手の簡単な機能だったら、(時間があれば)解決方法を模索する(ライブラリを探すじゃなく)のもプログラムの醍醐味だったりする。

とはいえ、品質で考えたらSTLやboostの方が断然上(テスト量が桁違うし)なので、そのうち今回と同機能を見つけたら、このコードは破棄するつもり。更にドキュメントとか仕様とかしっかりしているので、チーム開発における「情報伝搬」・「認識合わせ」の手間が省けて効率的。

ただ、自分であれやこれやと実装方法を模索した後に、ライブラリに出会えれば、答え合わせが楽しいし。:grinning:
(ある意味、贅沢な方法かもですね)

さて。。。 こいつと同機能なライブラリを探してみようかしら。。。

神が降臨しました(2021/03/08 追記)

哀れな迷える子羊に神が降臨しました。
@myoukaku さんありがとうございます!
@myoukaku さんのコードには本質的な手法以外にも情報が詰まっておりまして、神様のお告げにはこう書かれていました。

  • template における再帰の手法はこうだよ
  • int じゃなく std::size_t 使おうね
  • static_assert でテストコード書けるよ
  • structはアクセス指定子を指定しなければpublic継承になるよ

神様ごめんなさい。。。
いや、ありがとうございます。

神様のコードを理解するまで眺めて。。。
コードを閉じる → 頭でイメージ → シャワーを浴びる → ごはんを食べる
そして、書いてみました!1

#include <tuple>

template<std::size_t, std::size_t, typename ...> struct omit_impl;

template <std::size_t N, std::size_t I, typename ...TPARGS, typename T, typename ...ARGS>
  struct omit_impl<N, I, std::tuple<TPARGS...>, T, ARGS...> :
    omit_impl<N, I + 1, std::tuple<TPARGS..., T>, ARGS...> {};

template <std::size_t N, typename ...TPARGS, typename T, typename ...ARGS>
  struct omit_impl<N, N, std::tuple<TPARGS...>, T, ARGS...> :
    omit_impl<N, N + 1, std::tuple<TPARGS... /*, T */>, ARGS...> {};

template <std::size_t N, std::size_t I,typename ...TPARGS>
  struct omit_impl<N, I, std::tuple<TPARGS...>>
{
  using type = std::tuple<TPARGS...>;
};

template <std::size_t N, typename ...ARGS>
  struct omit :
    omit_impl<N, 0, std::tuple<>, ARGS...> {};

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

  static_assert(std::is_same<omit<0>::type, std::tuple<>>::value);

  static_assert(std::is_same<omit<0, T0>::type, std::tuple<>>::value);

  static_assert(std::is_same<omit<0, T0, T1>::type, std::tuple<T1>>::value);
  static_assert(std::is_same<omit<1, T0, T1>::type, std::tuple<T0>>::value);
  
  static_assert(std::is_same<omit<0, T0, T1, T2>::type, std::tuple<T1, T2>>::value);
  static_assert(std::is_same<omit<1, T0, T1, T2>::type, std::tuple<T0, T2>>::value);
  static_assert(std::is_same<omit<2, T0, T1, T2>::type, std::tuple<T0, T1>>::value);

  static_assert(std::is_same<omit<0, T0, T1, T2, T3>::type, std::tuple<T1, T2, T3>>::value);
  static_assert(std::is_same<omit<1, T0, T1, T2, T3>::type, std::tuple<T0, T2, T3>>::value);
  static_assert(std::is_same<omit<2, T0, T1, T2, T3>::type, std::tuple<T0, T1, T3>>::value);
  static_assert(std::is_same<omit<3, T0, T1, T2, T3>::type, std::tuple<T0, T1, T2>>::value);

  static_assert(std::is_same<omit<0, T0, T1, T2, T3, T4>::type, std::tuple<T1, T2, T3, T4>>::value);
  static_assert(std::is_same<omit<1, T0, T1, T2, T3, T4>::type, std::tuple<T0, T2, T3, T4>>::value);
  static_assert(std::is_same<omit<2, T0, T1, T2, T3, T4>::type, std::tuple<T0, T1, T3, T4>>::value);
  static_assert(std::is_same<omit<3, T0, T1, T2, T3, T4>::type, std::tuple<T0, T1, T2, T4>>::value);
  static_assert(std::is_same<omit<4, T0, T1, T2, T3, T4>::type, std::tuple<T0, T1, T2, T3>>::value);
}

今回の神様のお言葉で特に感銘を受けたのは特殊化のパラメータで std::tuple<TPARGS...>TPARGSをキャプチャする箇所と、omit_impl<N, N, で 値が同一という表現をする箇所が、グッときました。
言われれば、そりゃそうなんだけど、何と言うか。。。特殊化の面白しろさというか奥の深さを知った感じです。

@myoukaku さんのお陰で楽しい週末になりました。
ありがとうございます!

新しい技を覚えたので(2021/03/09 追記)

その後、@myoukaku さんから std::conditional という新しい技を伝授いただいたので、リファクタリングしました。

template<std::size_t, std::size_t, typename ...> struct omitargs_impl;

template <std::size_t N, std::size_t I, typename ...TPARGS, typename T, typename ...ARGS>
  struct omitargs_impl<N, I, std::tuple<TPARGS...>, T, ARGS...> :
    std::conditional<
      I == N,
      omitargs_impl<N, I + 1, std::tuple<TPARGS...>, ARGS...>,
      omitargs_impl<N, I + 1, std::tuple<TPARGS..., T>, ARGS...>
    >::type {};

template <std::size_t N, std::size_t I,typename ...TPARGS>
  struct omitargs_impl<N, I, std::tuple<TPARGS...>>
{
  using type = std::tuple<TPARGS...>;
};

template <std::size_t N, typename ...ARGS> struct omitargs {
  static_assert(N >= 0, "N < 0");
  static_assert(N < std::tuple_size<std::tuple<ARGS...>>::value, "N >= ARGS");
  using type = typename omitargs_impl<N, 0, std::tuple<>, ARGS...>::type;
};

template <std::size_t N, typename ...ARGS> struct omitargs_r {
  static_assert(N >= 0, "N < 0");
  static_assert(N < std::tuple_size<std::tuple<ARGS...>>::value, "N >= ARGS");
  using type = typename omitargs<std::tuple_size<std::tuple<ARGS...>>::value - N - 1, ARGS...>::type;
};

この前のコードでは<N, N のあたりが「技使ってるで~カッコエエ~」的な感じ(主観です)でしたが、こちらは地味ながらも(主観です)条件分岐が明確なので、後から読んで理解しやすいなぁ~と思います。

  1. 実は当初はN番目が1始まりでしたが、神様の仕様(0始まり)の方が直感的なので仕様変更しました。

2
2
2

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
2
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?