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.

C++ のタプルの全要素を逐次処理する。標準ライブラリのみ、 C++17 以上。

Last updated at Posted at 2021-10-15

Boost を使わずに、std::tuple に使える拡張 for 文のようなものが欲しい人向け。C++17 以上。

#include <iostream>
#include <tuple>

/**
 * @fn void ApplyTuple(const F& func, const T& tpl, std::index_sequence<indices...>)
 * @brief タプル tpl の全要素に関数 func を適用する。
 * @param[in] func タプルの要素を引数にとる関数。
 * @param[in] tpl タプル。
 * @return void
 */
template <typename F, typename T, std::size_t... indices>
void ApplyTuple(const F& func, const T& tpl, std::index_sequence<indices...>) {
  using swallow = int[];
  (void)swallow {
    (func(std::get<indices>(tpl)), 0)... 
  };
};

/**
 * @fn R ApplyTuple(const F& func, const T& tpl, std::index_sequence<indices...>)
 * @brief タプル tpl の全要素に関数 func を適用し、その結果を得る。
 * @param[in] func タプルの要素を引数にとる関数。
 * @param[in] tpl タプル。
 * @return R タプル tpl の各要素に関数 func を適用させた値を要素に持つタプル。
 */
template <typename R, typename F, typename T, std::size_t... indices>
R ApplyTuple(const F& func, const T& tpl, std::index_sequence<indices...>) {
  R ret;
  using swallow = int[];
  (void)swallow {
    (ret = std::make_tuple(func(std::get<indices>(tpl))...), 0)
  };
  return ret;
};

int main(void) {
  /** 戻り値を受け取らない場合 */
  // タプルを生成。
  auto tpl1 = std::make_tuple("abc", 1, 1.2);
  // タプルのサイズを取得。
  constexpr std::size_t size1 = std::tuple_size<decltype(tpl1)>::value;
  // タプルの要素に適用する関数。ここでは、要素を標準出力。
  auto f1 = [](auto t){
    std::cout << t << std::endl;
  };
  // tpl1 の各要素に f1 を適用。
  ApplyTuple(f1, tpl1, std::make_index_sequence<size1>());

  /** 戻り値を受け取る場合 */
  // タプルを生成。
  auto tpl2 = std::make_tuple(1, 1.2);
  // タプルのサイズを取得。
  constexpr size_t size2 = std::tuple_size<decltype(tpl2)>::value;
  // タプルの要素に適用する関数。ここでは、要素に 1 を足した結果を返す。
  auto f2 = [](auto t){
    return t + 1;
  };
  // 戻り値の型を指定。
  using Rv = std::tuple<int, double>;
  // 戻り値の型を指定して、tupl2 の各要素に f2 を適用。
  auto rv = ApplyTuple<Rv>(f2, tpl2, std::make_index_sequence<size2>());
  // 結果の確認
  std::cout << std::get<0>(rv) << std::endl; //=> 2
  std::cout << std::get<1>(rv) << std::endl; //=> 2.2

  return 0;
};

タプルの各要素に関数を適用した結果を、

  1. 受け取らない場合
  2. タイプ型として受け取る場合

の 2 種類用意しました。

タプルの要素への副作用を期待する場合の注意点

タプルへの副作用を期待して、ラムダ式の引数をコピー渡し auto から参照渡し auto& にする場合、注意が必要です。

というのも、タプルの要素は const 修飾されており、そのままでは値を変更することができないからです。

自作クラスのインスタンスを要素に持つタプルを逐次処理して、タプルの要素である各インスタンスにメンバー関数を順次適用させていく場合には、自作クラスのメンバー変数を mutable 修飾したり、メンバー関数を const 修飾したりする必要があります。

コードで見てみます。

// ...
// 上に ApplyTuple 等のコード

template <typename T>
class A {
public:
  // const メンバー関数から値を変更するメンバー変数には mutable 修飾が必要。
  mutable T value_;

  // const 修飾されたインスタンスに適用させたいメンバー関数には const 修飾が必要。
  void Add(const T val) const {
    value_ += val;
  };
};

int main(void) {
  auto a1 = A<int>();
  a1.value_ = 1;

  auto a2 = A<double>();
  a2.value_ = 1.2;

  // tpl の要素は const 修飾されている。
  auto tpl = std::make_tuple(a1, a2);
  constexpr size_t size = std::tuple_size<decltype(tpl)>::value;
  // 参照渡し auto& にしている。
  auto f = [](auto& a) {
    a.Add(1);
  };
  ApplyTuple(f, tpl, std::make_index_sequence<size>());

  std::cout << std::get<0>(tpl).value_ << std::endl; //=> 2
  std::cout << std::get<1>(tpl).value_ << std::endl; //=> 2.2

  return 0;
};

補足

個人的には、以下の違いが勉強になりました。

  • エラーになる例
// 整数が indice に実行時パラメータとして渡り、エラーになる。
template <typename T, typename... Indices>
void ApplyTuple(T tpl, Indices... indices) {
  using swallow = int[];
  (void)swallow {
    (std::cout << std::get<indices>(tpl) << std::endl, 0)... 
  };
};

int main(void) {
  auto tpl = std::make_tuple("abc", 1, 1.2);
  constexpr size_t size = std::tuple_size<decltype(tpl)>::value;
  ApplyTuple(tpl, 0, 1, 2);
  return 0;
}
  • エラーにならない例
// 整数が indices に非型テンプレートパラメータとして渡り、エラーにならない。
template <typename T, std::size_t... indices>
void ApplyTuple(T tpl, std::index_sequence<indices...>) {
  using swallow = int[];
  (void)swallow {
    (std::cout << std::get<indices>(tpl) << std::endl, 0)... 
  };
};

int main(void) {
  auto tpl = std::make_tuple("abc", 1, 1.2);
  constexpr size_t size = std::tuple_size<decltype(tpl)>::value;
  ApplyTuple(tpl, std::make_index_sequence<size>());
  return 0;
}

参考

1
2
3

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?