動機: boost::optionalだけじゃなくboost::eitherがほしい - Faith and Brave - C++で遊ぼう
Boost.Optional は C++ でも屈指の便利ライブラリですが、
「戻り値として使う」場合には、時々「失敗した」という状況の他に、失敗した状況を示すような値が返せると嬉しい場合があります。
つまり、関数本来の戻り値の代わりに、それとは別の型でエラー情報を返すことも出来る、
要するに Haskell で言うところの Either a b があるといい、ということです。
これを C++ で実現するためには、一見して Boost の Variant を使うことで
boost::variant<string, double> sqrt_either( double x ) { if( x < 0 ){ return "negative number!"; } return std::sqrt(x); }
とかやればいいように思えますが、これだと、
boost::variant
が作れない、という、どうしようもない弱点があります。
そこで、同じ型であっても格納出来るように改良した boost::variant、
すなわち either があるといいな、と思って、ちょっと作ってみました。
こんな感じで使います:
namespace gintenlib { template<typename Left, typename Right> struct either; // 他にも left とか right とか apply_visitor とか apply_either とか } #include <iostream> struct print_t { typedef void result_type; template<typename T> void operator()( T const& x ) const { std::cout << x << std::endl; } } print; // エラー表示関数 void error( std::string const& what ) { std::cout << "Error: " << what << std::endl; } int main() { using std::string; // Left と Right が違う場合 // 構築は値から直接行える { typedef gintenlib::either<string, int> either_type; either_type const x = 1, y = "hoge"; // Visitor の適用 // Boost.Variant と同じように使える either_type z; apply_visitor( print, z ); // apply_either は Haskell の either 関数相当 // either の中身が Left なら第一引数を、 // either の中身が Right なら第ニ引数を、 // 第三引数に適応する。 z = x; apply_either( print, print, z ); z = y; apply_visitor( print, y ); } // left と right が同じ/相互変換可能な場合 // left() と right() で明示的に指定する { typedef gintenlib::either<string, string> either_type; using gintenlib::left; using gintenlib::right; either_type const x = left("fail"), y = right("OK."); either_type z = x; // 今度は手で分岐してみる // このように if の条件部で right() や left() の戻り値を受ければ // 安全に分岐処理を行える(ただし参照なので一時変数を使った場合は注意が必要) if( boost::optional<string&> const r = z.right() ) { std::cout << "right: " << *r << std::endl; } else if( boost::optional<string&> const l = z.left() ) { std::cout << "left: " << *l << std::endl; } // でも基本的には apply_either を使った方がいい z = y; apply_either( &error, print, z ); // 部分適用もできたりする // ( ADL の関係で名前空間修飾が必要) gintenlib::apply_either( &error, print )( x ); } }
実装は http://ideone.com/qURcu を御覧下さい。
やってることは単純に、Boost.Variant に持たせる型をラップして、区別出来るようにしているだけです。
ただ、それだけだと楽しくないので、 Left と Right が無関係の型の場合は、
ラップしていない生の値から暗黙変換が出来るように、 SFINAE を使って切り分けています。