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>);
}