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;
};
タプルの各要素に関数を適用した結果を、
- 受け取らない場合
- タイプ型として受け取る場合
の 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;
}