Reputation: 311
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(...) {
}
}
Upvotes: 0
Views: 80
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)...);
}
Note, I changed your "push_front" into "push_back" to have correct order
Upvotes: 1