adziri
adziri

Reputation: 311

Fallthrough in pattern matching

Recently there was a need to write a matching pattern for catching errors, which it actually copes with. But the only thing it lacks in comparison with the same switch is the opportunity to fallthrough. All my attempts to implement this feature have been unsuccessful. Initially, I thought about something like this:

matcher(errcode)
        .match(SQLITE_MISUSE    , [] { return test::Fallthrough{}; })
        .match(SQLITE_CONSTRAINT, [] { return test::Fallthrough{}; })
        .match(SQLITE_ERROR     , [] { throw std::runtime_error("XXX"); })
        .match(SQLITE_BUSY      , [] { })
        ;

But I failed to implement it. So, below, I am attaching my draft "unsuccessful" version of the code. I would like to somehow implement it all the same, I will be glad for any advice.

#include <iostream>
#include <tuple>
#include <optional>
#include <type_traits>
#include <utility>
#include <optional>

namespace test {

namespace detail {

struct Void final {};

template <typename T> T operator,(T const& v, Void) noexcept {
    return v;
}

} // namespace detail

template <typename T, typename Fn> struct Match_pack {
    T  v;
    Fn fn;
};

struct Fallthrough {};

template <typename T> bool invoke(T) {
    return false;
}

template <typename T, typename Pack, typename... Tail>
bool invoke(T v, Pack&& pack, Tail&&... tail) {
    if (pack.v == v) {
        auto rv = (pack.fn(), detail::Void{});
        // ...
        return true;
    }
    return invoke(v, std::forward<Tail>(tail)...);
}

template <typename...> struct Match_args;

template <typename... Args> struct Matcher {

    Matcher(Args... args) : vs_{args...}
    { }

    template <typename E, typename Fn> auto match(E e, Fn fn)
        -> Match_args<Matcher<Args...>, Match_pack<E, Fn>>
    {
        return {std::move(*this), {e, fn}};
    }

protected:
    std::tuple<Args...> vs_;
};

template <typename... Args, typename... Tail>
struct Match_args<Matcher<Args...>, Tail...> : Matcher<Args...>, Tail... {

    Match_args(Matcher<Args...>&& args, Tail&&... tail)
        : Matcher<Args...>{std::move(args)}
        , Tail(std::move(tail))...
    { }

    template <typename E, typename Fn> auto match(E e, Fn fn) 
        -> Match_args<Matcher<Args...>, Match_pack<E, Fn>, Tail...>
    {
        return {std::move(*this), {e, fn}, std::forward<Tail>(*this)...};
    }

    operator bool() {
        return std::apply([&](auto... vs) {
            return (invoke<decltype(vs), Tail...>(vs, std::forward<Tail>(*this)...) && ...);
        }, this->vs_);
    }
};

} // namespace test


enum {
    SQLITE_ERROR,
    SQLITE_MISUSE,
    SQLITE_CONSTRAINT,
    SQLITE_DONE,
    SQLITE_ROW,
    SQLITE_BUSY
};

auto step() {
    return SQLITE_CONSTRAINT;   
}

template <typename... Args>
auto matcher(Args... args) {
    return test::Matcher{args...}; 
}

std::optional<int> foo() {
    auto errcode = step();
    if (SQLITE_DONE == errcode) return std::nullopt;
    if (SQLITE_ROW  == errcode) return 1;

    bool f = matcher(errcode)
        .match(SQLITE_MISUSE    , [] { std::cout << "SQLITE_MISUSE\n";     return test::Fallthrough{}; })
        .match(SQLITE_CONSTRAINT, [] { std::cout << "SQLITE_CONSTRAINT\n"; return test::Fallthrough{}; })
        .match(SQLITE_ERROR     , [] { std::cout << "SQLITE_ERROR\n"; throw std::runtime_error("XXX"); })
        .match(SQLITE_BUSY      , [] { std::cout << "SQLITE_BUSY\n" ; })
        ;

    std::cout << f;
    return 2;
}

int main() {
    try {
        auto x = foo();
    } catch(...) {
    }
}

Godbolt link

Upvotes: 0

Views: 80

Answers (1)

Jarod42
Jarod42

Reputation: 218238

You might check return type of your "case":

template <typename T, typename Pack, typename... Tail>
bool invoke(T v, Pack&& pack, Tail&&... tail) {
    if (pack.v == v) {
        auto rv = (pack.fn(), detail::Void{});
        if constexpr (std::is_same_v<Fallthrough, decltype(rv)>) {
            static_cast<void>( // To avoid warning for clang
                ((tail.fn(), std::is_same_v<Fallthrough, decltype(tail.fn())>) && ...)
            );
        }
        return true;
    }
    return invoke(v, std::forward<Tail>(tail)...);
}

Demo

Note, I changed your "push_front" into "push_back" to have correct order

Upvotes: 1

Related Questions