如何在C++自定义模式匹配工具中实现switch同款fallthrough特性
实现思路
核心要对齐原生switch的fallthrough行为:
- 优先找到第一个值匹配的分支,执行对应回调
- 若回调返回
Fallthrough类型,则跳过后续分支的值匹配校验,直接按定义顺序依次执行后续所有分支的回调 - 直到某回调不返回
Fallthrough(返回void或其他类型),终止执行 - 无任何匹配分支返回false,有匹配分支返回true
关键修改点
- 调整
match方法的模板参数顺序,保证分支执行顺序和代码书写顺序一致 - 新增辅助逻辑判断回调返回值类型,区分是否需要继续fallthrough
- 重写
invoke递归逻辑:先定位首个匹配分支,再从该分支开始逐次执行后续回调,按需穿透 - 处理void返回值的回调默认终止穿透的逻辑
完整可运行代码
#include <iostream> #include <tuple> #include <optional> #include <type_traits> #include <utility> #include <functional> namespace test { struct Fallthrough {}; namespace detail { // 辅助判断返回值是否为Fallthrough template<typename T> constexpr bool is_fallthrough_v = std::is_same_v<std::decay_t<T>, Fallthrough>; // 处理回调返回值,返回是否需要继续穿透 template<typename R> bool handle_return(R&& rv) { if constexpr (is_fallthrough_v<R>) { return true; } else { return false; } } // 回调返回void的情况,默认不穿透 bool handle_return(void) { return false; } } // namespace detail template <typename T, typename Fn> struct Match_pack { T v; Fn fn; }; // 递归终止:无剩余分支,返回是否已经匹配过 template <typename T> bool invoke(T /*v*/, bool already_matched) { return already_matched; } template <typename T, typename Pack, typename... Tail> bool invoke(T v, bool already_matched, Pack&& pack, Tail&&... tail) { bool need_exec = already_matched; // 还没匹配过的话先判断值是否匹配 if (!already_matched && pack.v == v) { need_exec = true; } if (need_exec) { // 执行回调 if constexpr (std::is_void_v<std::invoke_result_t<decltype(pack.fn)>>) { std::invoke(pack.fn); // 返回void,终止穿透 return true; } else { auto rv = std::invoke(pack.fn); if (detail::handle_return(rv)) { // 需要穿透,继续执行后面的分支 return invoke(v, true, std::forward<Tail>(tail)...); } else { // 不需要穿透,直接返回 return true; } } } // 不需要执行当前分支,继续找下一个 return invoke(v, false, 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...>, Tail..., Match_pack<E, Fn>> { return {std::move(*this), std::forward<Tail>(*this)..., {e, fn}}; } operator bool() { return std::apply([&](auto... vs) { return (invoke(vs, false, static_cast<Tail&>(*this)...) && ...); }, this->vs_); } }; } // namespace test enum { SQLITE_ERROR, SQLITE_MISUSE, SQLITE_CONSTRAINT, SQLITE_DONE, SQLITE_ROW, SQLITE_BUSY }; auto step() { return SQLITE_MISUSE; } 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(const std::runtime_error& e) { std::cout << "catch exception: " << e.what() << "\n"; } }
运行效果说明
当step()返回SQLITE_MISUSE时,输出顺序为:
SQLITE_MISUSE SQLITE_CONSTRAINT SQLITE_ERROR catch exception: XXX
完全符合预期的fallthrough行为,和原生switch写了三个连续fallthrough的case效果一致。
内容的提问来源于stack exchange,提问作者adziri
相关产品推荐
相关产品推荐

