1
\$\begingroup\$

To practice with SFINAE, template meta programming, functional programming and monads in C++, I decided to try writing a skinny version of the two functions (monadic binding and return) that make std::optional a monad.

Here's the result:

#include <optional>
#include <utility>
#include <vector>

template<typename T, typename TD = std::decay_t<T>, typename = void>
struct is_optional : std::false_type {};

template<typename T, typename TD>
struct is_optional<
    T,
    TD,
    std::enable_if_t<std::is_same_v<TD,std::optional<typename TD::value_type>>>
    > : std::true_type {};

template<typename T>
inline constexpr bool is_optional_v = is_optional<T>::value;

template<typename T, typename F, typename = void>
struct MBind {
    inline constexpr auto operator()(T&&, F&&) const noexcept = delete;
};

template<typename T, typename F>
struct MBind<T, F, std::enable_if_t<
    is_optional_v<T> && is_optional_v<decltype(std::declval<F>()(std::declval<T>().value()))>
    >> {
    template<typename Opt = T, typename Fun = F>
    inline constexpr auto operator()(Opt&& opt, Fun&& f) const noexcept {
        return opt ? f(*std::forward<T>(opt)) : decltype(f(*std::forward<T>(opt))){};
    }
};

inline constexpr auto mbind = [](auto&& t, auto&& f) noexcept {
    return MBind<decltype(t), decltype(f)>{}(
            std::forward<decltype(t)>(t),std::forward<decltype(f)>(f)
            );
};

inline constexpr auto mreturn = [](auto&& x){
    return std::make_optional(std::forward<decltype(x)>(x));
};

int main()
{
    auto xxx1 = mbind(std::optional<int>{1}, [](auto){ return mreturn(3.3); }); // Ok
    //auto xxx2 = mbind(std::optional<int>{1}, [](auto){ return 1; });          // wrong F
    //auto xxx3 = mbind(std::vector<int>{1,2}, [](auto){ return mreturn(3); }); // wrong T
}

A few points on which I'd like to receive some feedback, beside any other comments that the code above needs.

  • I know I've written a meta-function is_optional_v which doesn't really enforce that T satisfies the general concept of a maybe, but requires that it is exactly a std::optional; my target was not to learn writing concepts (yet).
  • For invalid types I've deleted the operator() that actually does the binding, so I have no place to put any message like "this is not a `std::optional`, man"; the same hold also for the metafunction is_optional_v; actually, probably the latter, not MBind::operator(), should be responsible for issueing a compiler message about the input being not a std::optional, whereas MBind::operator() should probably be responsible for complaining about the monadic function passed in. (Probably I think this is really a matter of personal preference, as I think this is what Louis Dionne says here.)
  • I have tried to perfect forwarding things whenever it makes sense, but I'm not really sure I've done it the right way.
  • I have templated MBind and not (just) its operator() to allow me partial specialization so that I could use the SFINAE-std::enable_if idiom.
  • I have defaulted the template parameters of operator() to those of the class, because (as explained above) I didn't want to make everything that behaves like an optional a monad; I think I would do that via tag dispatching when I get more comfortable with this stuff.
  • Have I exagerated with inline and constexpr? Or, is any of those superfluous or inherently a bad idea?
\$\endgroup\$
1

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.