Skip to content

Instantly share code, notes, and snippets.

@cleoold
Created December 2, 2020 06:52
Show Gist options
  • Save cleoold/0f1465f21a926abc37f8334c5f480c34 to your computer and use it in GitHub Desktop.
Save cleoold/0f1465f21a926abc37f8334c5f480c34 to your computer and use it in GitHub Desktop.
maybe type with funny operator overloading
#include <iostream>
#include <string>
#include <variant>
#include <tuple>
// https://gist.github.com/cleoold/df31c3789f2b2b8fe2cfa5022957ee06
#include "lambda_traits.hpp"
template<class T>
struct decay_maybe { using type = T; };
template<class T>
class Just {
T value;
public:
template<class Arg>
constexpr Just(Arg &&v) : value(std::forward<Arg>(v)) {}
auto constexpr unwrap() {
return std::move(value);
}
template<class F>
friend auto constexpr operator>>=(const Just &just, F &&f) {
return std::forward<F>(f)(just.value);
}
friend auto &operator<<(std::ostream &os, const Just &just) {
os << "Just<" << just.value << '>';
return os;
}
template<class F1, class F2>
requires (is_lambda<std::remove_cvref_t<F1>>::value && is_lambda<std::remove_cvref_t<F2>>::value)
friend auto operator>>=(F1 &&f1, F2 &&f2);
};
template<class T>
Just(T) -> Just<T>;
class Nothing {
public:
template<class F>
friend auto constexpr operator>>=(const Nothing &nothing, F &&f) {
return nothing;
}
friend auto &operator<<(std::ostream &os, const Nothing &) {
os << "Nothing";
return os;
}
};
template<class T>
class Maybe {
std::variant<Just<T>, Nothing> value;
public:
template<class Arg>
constexpr Maybe(Arg &&v) : value(std::forward<Arg>(v)) {}
constexpr Maybe() : value{Nothing{}} {}
auto constexpr unwrap(const T &default_) {
return std::visit([&default_] (auto &&v) {
using TVariant = std::remove_cvref_t<decltype(v)>;
if constexpr (std::is_same_v<TVariant, Just<T>>)
return v.unwrap();
else
return default_;
}, value);
}
template<class F>
friend auto constexpr operator>>=(const Maybe &maybe, F &&f) {
// if func returns Nothing directly, do not make Maybe<Nothing>(Nothing())
// instead inherit the same type T: Maybe<T>(Nothing())
using TestR = typename decay_maybe<decltype(f(std::declval<T>()))>::type;
using R = std::conditional_t<std::is_same_v<TestR, Nothing>, T, TestR>;
return std::visit([&f] (auto &&v) -> Maybe<R> {
return std::forward<decltype(v)>(v) >>= f;
}, maybe.value);
}
friend auto &operator<<(std::ostream &os, const Maybe &maybe) {
std::visit([&os] (auto &&v) {
os << "Maybe<" << std::forward<decltype(v)>(v) << '>';
}, maybe.value);
return os;
}
template<class F1, class F2>
requires (is_lambda<std::remove_cvref_t<F1>>::value && is_lambda<std::remove_cvref_t<F2>>::value)
friend auto operator>>=(F1 &&f1, F2 &&f2);
};
template<class T>
Maybe(T) -> Maybe<T>;
template<class T>
struct decay_maybe<Maybe<T>> { using type = T; };
template<class T>
struct decay_maybe<Just<T>> { using type = T; };
template<class T>
struct is_just { static constexpr auto value = false; };
template<class T>
struct is_just<Just<T>> { static constexpr auto value = true; };
template<class T>
struct is_maybe { static constexpr auto value = false; };
template<class T>
struct is_maybe<Maybe<T>> { static constexpr auto value = true; };
//////////////////////////////////////////////////////
template<class F1, class F2>
requires (is_lambda<std::remove_cvref_t<F1>>::value && is_lambda<std::remove_cvref_t<F2>>::value)
auto operator>>=(F1 &&f1, F2 &&f2) {
using F1Traits = lambda_traits<std::remove_const_t<std::remove_reference_t<F1>>>;
using F1Arg = typename F1Traits::template arg_type_at<0>;
using F1Ret = typename F1Traits::result_type;
using F2Traits = lambda_traits<std::remove_const_t<std::remove_reference_t<F2>>>;
using F2Ret = typename F2Traits::result_type;
return [f1 = std::forward<F1>(f1), f2 = std::forward<F2>(f2)] (F1Arg x) {
auto f1ret = f1(std::forward<decltype(x)>(x));
if constexpr (std::is_same_v<F1Ret, Nothing>)
return f1ret;
else if constexpr (is_just<F1Ret>::value)
return f2(f1ret.value);
else if constexpr (is_maybe<F1Ret>::value) {
using F2Ret = typename decay_maybe<F2Ret>::type;
using R = std::conditional_t<std::is_same_v<F2Ret, Nothing>, F1Ret, F2Ret>;
return std::visit([f2] (auto &&v) -> Maybe<R> {
using VT = std::remove_cvref_t<decltype(v)>;
if constexpr (std::is_same_v<VT, Nothing>)
return v;
else
return f2(std::forward<decltype(v)>(v).value);
}, f1ret.value);
} else
return f2(f1ret);
};
}
//////////////////////////////////////////////////////
#define fn [&]
#define πŸ‘’(...) { return (__VA_ARGS__); }
int main() {
// return values directly
auto val = Maybe(16)
>>= fn (int x) πŸ‘’ (x + 1)
>>= fn (int x) πŸ‘’ (x * 2)
>>= fn (int x) πŸ‘’ (x - 4.4);
auto val2 = Maybe(16)
>>= fn (int x) πŸ‘’ (x + 1)
>>= fn (int) πŸ‘’ (Nothing())
>>= fn (int x) πŸ‘’ (x - 4.4);
std::cout << val << std::endl;
std::cout << " " << val.unwrap(0) << std::endl;
std::cout << val2 << std::endl;
std::cout << " " << val2.unwrap(0) << std::endl;
// return maybes
auto val3 = Maybe(17)
>>= fn (int x) πŸ‘’ (Maybe(x + 10))
>>= fn (int x) πŸ‘’ (x > 18 ? Maybe(x * 2.2) : Maybe<double>())
>>= fn (double x) πŸ‘’ (Maybe(std::to_string(x) + "lf"));
auto val4 = Maybe(7)
>>= fn (int x) πŸ‘’ (Maybe(x + 10))
>>= fn (int x) πŸ‘’ (x > 18 ? Maybe(x * 2.2) : Maybe<double>())
>>= fn (double x) πŸ‘’ (Maybe(std::to_string(x) + "lf"));
std::cout << val3 << std::endl;
std::cout << " " << val3.unwrap("none") << std::endl;
std::cout << val4 << std::endl;
std::cout << " " << val4.unwrap("none") << std::endl;
}
Maybe<Just<29.6>>
29.6
Maybe<Nothing>
0
Maybe<Just<59.400000lf>>
59.400000lf
Maybe<Nothing>
none
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment