はじめに
この記事では、ある要求に対して、筆者が気づいた1つの解決策を載せています。もっと拡張できる!や、全く別のアプローチがある!などの発見をぜひ楽しんでください。
要求
同一の型を格納したコレクションに対して、そのすべての要素に関数を適用したい。
コレクション
タイトルにあるように、サンプルとなるコレクションをstd::tuple で定義してみる。
enum class color { r, g, b };
const auto list = std::make_tuple(
color::r,
color::g,
color::b );
std::array のインスタンス化には必要な要素数の明示的な記述は、std::tuple では必要ない。これは、プログラムの拡張性を維持したい場合に嬉しい性質である。また、要素へのアクセスにはアクセサ(std::get)を通すため、不正な操作を防止しやすい。
実装
早速、map “風”の関数適用の実装をお見せしたい。
アイデアはとても簡単で、tuple の要素へアクセスするための std::get に渡すテンプレートパラメータを、再帰呼び出しと部分特殊化によって満たす。ただし、注意したいのは、そのパラメータとなるインデックスが降順に適用されるところである。
template< typename T, size_t N = std::tuple_size<T>::value >
struct tuple_map
{
static void apply( const T& t, const std::function<void( const typename std::tuple_element<0, T>::type&, const size_t )>& map_func )
{
map_func( std::get<N-1>( t ), N-1 );
return tuple_map<T, N-1>::apply( t, map_func );
}
};
template< typename T >
struct tuple_map<T, 0>
{
static void apply( const T&, const std::function<void( const typename std::tuple_element<0, T>::type&, const size_t )>& ) {}
};
クラス名、関数名がもし適切ではないとしてもご容赦いただきたい。(ご助言、お待ちしております。)map_func 関数に対して、tuple 要素のインデックスを渡すことができるのも、この tuple_map の特徴である。
前提にあるように、tuple の要素はすべて同一の型であるので、map_func の型は決め打ちできる。(tuple の最初の要素の型を基準にする。)これを満たさない場合は、コンパイルエラーとなる。
使用例
前述のコレクションに対して使用する。
tuple_map<decltype(list)>::apply( list, []( const color& c, const size_t index ) { std::cout << index << static_cast<uint32_t>(c) << std::endl; } );
インデックスを降順に走査するので、b, g, r の順に値が出力される。
おわりに
筆者は皆さんと同様、要求に対してやや大げさな実装をしたような感覚を否めない。しかし、冗長なコードをなくし、スッキリさせることができるのは間違いない。tuple と template を使った1つの遊び、楽しんでいただけただろうか。