概要
ある関数 f()
をライブラリーのユーザーへ提供したいとき、仮に想定される引数が最大で4種類ある状況を想像してみよう:
parameter_x_type x;
parameter_y_type y;
parameter_z_type z;
parameter_w_type w;
f( x, y, z, w );
単純にはこのように使える関数を定義するのは簡単:
auto f
( const parameter_x& x
, const parameter_y& y
, const parameter_z& z
, const parameter_w& w
) { /* なにかしらの処理 */ }
では、表題にあるように、つまり次のようにユーザーに対して可能性のある引数の何れかを順序もどうでもよく受け入れる砂糖の甘さを感じる関数を提供したいとしたら?
f( x );
f( y, w );
f( z, w, x );
f( x, y, w, z);
/* ... その他の組み合わせのみなさん ... */
この記事ではこのような要求が生じた場合に柔軟に対応する方法を示したい。
方法
「関数 f()
を例に、 area_type
や tag_type::vector_type
keyword_type::vector_type
などの適当な引数型を任意数任意順に受け、受け取った引数に基いて適当な処理を行って location_type
を返す」と言った具合の実装エグザンプルを示します:
# include <boost/optional.hpp>
# include <vector>
# include <string>
# include <iostream>
namespace some_library
{
using boost::optional;
template < typename T > struct has_vector_type { using vector_type = std::vector< T >; };
struct area_type { float a, b, c, d; };
struct tag_type : has_vector_type< tag_type > { std::string value; tag_type( const std::string& in ) : value( in ) { } };
struct keyword_type : has_vector_type< keyword_type > { std::string value; keyword_type( const std::string& in ) : value( in ) { } };
struct location_type : has_vector_type< tag_type > { };
// for library internal implements
namespace detail
{
struct f_state { };
auto f( const f_state& state )
{
std::cout << "c(execute state)";
/* TODO: ここで state から適切な処理を行い結果を return */
return location_type::vector_type{ };
}
template < typename T, typename ... TS >
auto f( const f_state& state, const T& value, TS ... ts )
{
std::cout << "b:0(configure state) -> ";
/* TODO: ここで T value を state へ適用 */;
return f( state, ts ... );
}
template < typename ... TS >
auto f( const f_state& state, const area_type& value, TS ... ts )
{
std::cout << "b:1(configure state by area_type) -> ";
/* TODO: */;
return f( state, ts ... ); }
}
// for library user
template < typename T, typename ... TS >
auto f( const T& value, TS ... ts )
{
std::cout << "a(make state) -> "; return detail::f( { }, value, ts ... );
}
}
auto main() -> int
{
using namespace some_library;
area_type area { 0, 1, 2, 3 };
tag_type::vector_type tags { { "aaa" }, { "bbb" }, { "ccc" } };
keyword_type::vector_type keywords { { "hoge" }, { "fuga" }, { "piyo" } };
std::cout << "1: ";
f( area );
std::cout << "\n";
std::cout << "2: ";
f( area, tags );
std::cout << "\n";
std::cout << "3: ";
f( area, tags, keywords );
std::cout << "\n";
std::cout << "4: ";
f( tags, keywords, area );
std::cout << "\n";
}
実行例
1: a(make state) -> b:1(configure state by area_type) -> c(execute state)
2: a(make state) -> b:1(configure state by area_type) -> b:0(configure state) -> c(execute state)
3: a(make state) -> b:1(configure state by area_type) -> b:0(configure state) -> b:0(configure state) -> c(execute state)
4: a(make state) -> b:0(configure state) -> b:0(configure state) -> b:1(configure state by area_type) -> c(execute state)
解説
variadic template により任意の型の引数を任意数受けられる f
をフロントエンド的に用意し、その内部では実際の処理の前処理として、 variadic template で受け取った引数をお約束の「先頭1つ+その他による尻尾」に分離して「先頭1つ」毎に適当なステートに引数に応じて必要な前処理を追加で施し、 variadic template で引き受けたすべての引数の前処理を終えた時点でそれまでに蓄積した state に応じて処理を行い結果を返す、と言った具合です。
この時、任意の T
型に対してでは処理が面倒な特定の型、上記の例では仮に例えば area_type
がそのような型だと仮定してテンプレートの特殊化によって T = area_type
の場合の処理を特殊化しています。必要に応じてテンプレートを特殊化します。
また、任意の型Tに対する元のテンプレート定義は asset
ないし throw std::logic_error( "undefined type" )
的に落としてしまうよう実装すれば、想定した特定の型の場合のみ動作します。
もし、組み合わせによっては実行に支障がある場合には、最終的な実行直前に state の整合性のチェックを入れます。