きままにブログ

プログラミングを主とした私のメモ帳です。寂しいのでコメントください笑

Optionalを実装せよ

Optionalは無効値を持った型です。

Optionalを作るときに、メモリ確保は行わず、スタック上の変数に対してplacement newによって再配置を行います。そのためオーバーヘッドは少ないはずです。

テストケース

正の数の場合は値を返し、そうでない場合は無効値を返す関数funcを考えます。

Optional<int> func(int n) {
	if(n > 0) {
		return Optional<int>(n);
	}
	else {
		return Optional<int>();
	}
}

int main() {
	std::array<int, 2> a = { 10, -10 };

	for(auto it : a) {
		if(auto v = func(it)) { // 型付代入をif文に入れる
			printf("正の数 : %d\n", *v);
		}
		else {
			printf("正の数を与えよ\n");
		}
	}
	

	getchar();
	return 0;
}

Maybeモナド

関数型プログラミングはよく知らないのでそれっぽい感じだとは思いますが、Optionalを続けて実行できるような演算子>>を定義してみました。

using t = Optional<const int>;
using t_none = None<const int>;

for(const auto& i : { t(60), t(5), t(30) }) {
	printf("=====\n");
	
	const auto ret = i
		>> [](const t& v) -> t {
		printf("In first lambda\n");
		if(*v > 10) {
			return *v / 10;
		}
		else {
			return t_none();
		}
	}
		>> [](const t& v) -> t {
		printf("In second lambda\n");
		if(*v % 2 == 0) {
			return *v;
		}
		else {
			return t_none();
		}
	};

	if(ret) {
		printf("ok. ret = %d\n", *ret);
	}
	else {
		printf("error.\n");
	}
}

実行結果はこうなります :

In first lambda
In second lambda
ok. ret = 6

In first lambda
error.

In first lambda
In second lambda
error.

エラーは無効値として返却されるので、頻繁に起こりうる例外はこちらで書いた方がいいかもしれませんね。

bindとか活用して複数回繰り返す

using namespace utils;
using t = Optional<const int>;
using t_none = None<const int>;

auto try_times = [](std::function<t(t)> func, size_t n, const t& v) -> t {
	t ret = v;
	for(size_t i = 0; i < n; ++i) {
		if(!(ret = ret >> func)) {
			return t_none();
		}
	}
	return ret;
};

const t i = 1;

const auto ret = i
	>> std::bind(try_times,
	[](const t& it) { return *it * 2; },
	10, std::placeholders::_1);

printf("%d", *ret);

実装例

#include <new>
#include <type_traits>
#include <functional>

template <typename T>
class None { };

template <typename T>
class Optional {
public:
	Optional() : data_pointer(nullptr) { }
	Optional(const T& it) : data_pointer(new(&data) T(it)) { }
	Optional(const None<T>& it) : data_pointer(nullptr) { }
	Optional(const Optional<T>& it) {
		if(data_pointer) { data_pointer = new(&data) T(*it); }
		else { data_pointer = nullptr; }
	}
	template <typename ...Args>
	Optional(Args ...args) : data_pointer(new(&data) T(args...)) { }
	~Optional() { if(data_pointer) { data_pointer->~T(); } }
	Optional<T> operator=(const T& it) {
		data_pointer = new(&data) T(it);
		return *this;
	}
	Optional<T> operator=(const Optional& it) {
		data_pointer = new(&data) T(*it);
		return *this;
	}
	Optional<T> operator=(const None<T>& it) {
		data_pointer = nullptr;
		return *this;
	}
	bool operator==(const Optional& it) const {
		return data_pointer == it.data_pointer;
	}
	bool operator!=(const Optional& it) const {
		return data_pointer != it.data_pointer;
	}
	T& operator*() const {
		return *data_pointer;
	}
	T* operator->() const {
		return data_pointer;
	}

	template <typename Func>
	Optional<T> operator>>(Func func) const {
		if(data_pointer != nullptr) {
			return func(*this);
		}
		else {
			return None<T>();
		}
	}
	operator bool() const {
		return data_pointer;
	}
private:
	mutable T* data_pointer;
	typename std::aligned_storage<sizeof(T), __alignof(T)>::type data;
};