HaskellのMaybeもどきをC++で書けるかなと思って書いてみました。本当にこんなんで良いのかどうかはかなり怪しいです。
#include <iostream> #include <string> namespace maybe { template<class T> class Maybe { public: Maybe(const T& t) : t_(t), fail_(false) {} Maybe() : fail_(true) {} public: template<class Func> typename Func::result_type operator>=(Func func) const { return fail_ ? typename Func::result_type() : func(t_); } private: T t_; bool fail_; template<class U> friend std::ostream& operator<<(std::ostream&, const Maybe<U>&); }; template<class T> Maybe<T> return_(const T& t) { return Maybe<T>(t); } template<class T> Maybe<T> fail(const std::string& s) { return Maybe<T>(); } template<class T> std::ostream& operator<<(std::ostream& os, const Maybe<T>& maybe) { if (maybe.fail_) os << "Nothing" << std::endl; else os << "Just " << maybe.t_ << std::endl; return os; } }
本当は>>=をオーバーロードしたいところですが、>>=は右結合のために使いにくいので替わりに>=で代用してあります。また、returnはC++の予約語なのでreturn_にしてあります。
例えば、標準入力から整数を読み取って平方根を計算し、標準出力に出力するとこんな感じでしょうか。
#include <cmath> #include "maybe.h" namespace { using namespace maybe; using namespace std; Maybe<int> read() { int n; cin >> n; return cin.fail() ? fail<int>("Could not read from cin.") : return_(n); } Maybe<double> sqrt_(int n) { return n < 0 ? fail<double>("Could not calculate sqrt of negative value.") : return_(sqrt(static_cast<double>(n))); } template<class T> Maybe<T> print(T t) { cout << t << endl; return cout.fail() ? fail<T>("Could not write to cout.") : return_(t); } } int main(int, char*[]) { cerr << (read() >= ptr_fun(&sqrt_) >= ptr_fun(&print<double>)); }
Maybeを抽象基底クラスにして、JustとNothingを派生クラスにするという手もあるなぁ、というかそっちの方が意味的に近いかも、と思って作った第二段。
仮想関数はテンプレート化できないので、渡せる関数の型が厳密に決まってしまいます(前の奴は変換可能なら何でも大丈夫でした)。Boost.Functionあたりを使うとこの辺はもう少しうまくできそうです。
#include <cmath> #include <iostream> #include <memory> #include <string> namespace maybe2 { template<class T> class Maybe { public: typedef std::auto_ptr<Maybe<T> > (*Func)(T); public: virtual ~Maybe() {} virtual std::auto_ptr<Maybe<T> > apply(Func func) const = 0; virtual void print(std::ostream& os) const = 0; }; template<class T> class Just : public Maybe<T> { public: Just(const T& t) : t_(t) {} virtual ~Just() {} virtual std::auto_ptr<Maybe<T> > apply(Func func) const { return func(t_); } virtual void print(std::ostream& os) const { os << "Just " << t_ << std::endl; } private: T t_; }; template<class T> class Nothing : public Maybe<T> { public: Nothing() {} virtual ~Nothing() {} virtual std::auto_ptr<Maybe<T> > apply(Func func) const { return std::auto_ptr<Maybe<T> >(new Nothing()); } virtual void print(std::ostream& os) const { os << "Nothing" << std::endl; } }; template<class T> std::auto_ptr<Maybe<T> > return_(const T& t) { return std::auto_ptr<Maybe<T> >(new Just<T>(t)); } template<class T> std::auto_ptr<Maybe<T> > fail(const std::string& s) { return std::auto_ptr<Maybe<T> >(new Nothing<T>()); } template<class T> std::auto_ptr<Maybe<T> > operator>=(const std::auto_ptr<Maybe<T> >& maybe, typename Maybe<T>::Func func) { return maybe->apply(func); } template<class T> std::ostream& operator<<(std::ostream& os, const std::auto_ptr<Maybe<T> >& maybe) { maybe->print(os); return os; } } namespace { using namespace maybe2; using namespace std; std::auto_ptr<Maybe<int> > read() { int n; cin >> n; return cin.fail() ? fail<int>("Could not read from cin.") : return_(n); } std::auto_ptr<Maybe<int> > sqrt_(int n) { return n < 0 ? fail<int>("Could not calculate sqrt of negative value.") : return_(static_cast<int>(sqrt(static_cast<double>(n)))); } template<class T> std::auto_ptr<Maybe<T> > print(T t) { cout << t << endl; return cout.fail() ? fail<T>("Could not write to cout.") : return_(t); } } int main(int, char*[]) { cerr << (read() >= &sqrt_ >= &print<int>); }