如何创建支持两种回调签名的C++20重试包装模板函数
问题描述
我有一个执行器,它接收待执行的函数作为参数,支持两种回调签名(取决于回调是否需要session对象):
template<typename T, typename S> T run(const std::function<T(Client&)>& f, S s); template<typename T, typename S> T run(const std::function<T(Client&, const S&)>& f, S s);
我想编写一个runWithRetry函数来包装这两个run,实现重试逻辑,但不想写两份几乎完全相同的代码。目前的重复实现如下:
template <typename T, typename S> T runWithRetry(const std::function<T(Client&)>& f, S s) { for (;;) { try { return run(f, s); } catch (Retry &e) {} } } template <typename T, typename S> T runWithRetry(const std::function<T(Client&, const S&)>& f, S s) { for (;;) { try { return run(f, s); } catch (Retry &e) {} } }
我尝试将回调设为模板参数,但需要正确推导返回类型,不知道如何实现能兼容两种签名的return_type。
解决方案(C++20)
利用C++20的特性可以统一实现逻辑,无需重复代码,同时自动推导返回类型。以下是几种可行方式:
方法1:decltype(auto) + 编译期分支判断
直接用模板参数接受任意可调用对象,通过if constexpr结合requires子句在编译期判断回调签名,自动匹配对应的run函数,decltype(auto)会自动推导返回类型:
#include <functional> #include <utility> // 前置声明依赖类型 class Client; class Retry; // 原run函数前置声明 template<typename T, typename S> T run(const std::function<T(Client&)>& f, S s); template<typename T, typename S> T run(const std::function<T(Client&, const S&)>& f, S s); // 统一的重试包装函数 template <typename F, typename S> decltype(auto) runWithRetry(F&& f, S&& s) { for (;;) { try { // 编译期判断回调是否支持带session的调用 if constexpr (requires(F&& func, Client& c, const S& sess) { func(c, sess); }) { return run(std::forward<F>(f), std::forward<S>(s)); } else { return run(std::forward<F>(f), std::forward<S>(s)); } } catch (Retry&) { // 捕获重试异常,继续循环 continue; } } }
方法2:用概念(Concepts)明确约束回调类型
通过定义概念区分两种回调签名,再重载runWithRetry,代码可读性更强:
#include <functional> #include <utility> class Client; class Retry; // 定义概念:不需要session的回调 template <typename F, typename S> concept NoSessionCallback = requires(F f, Client& c) { { f(c) } -> std::convertible_to<decltype(run(std::function{f}, std::declval<S>()))>; }; // 定义概念:需要session的回调 template <typename F, typename S> concept SessionCallback = requires(F f, Client& c, const S& s) { { f(c, s) } -> std::convertible_to<decltype(run(std::function{f}, std::declval<S>()))>; }; // 原run函数前置声明 template<typename T, typename S> T run(const std::function<T(Client&)>& f, S s); template<typename T, typename S> T run(const std::function<T(Client&, const S&)>& f, S s); // 重载1:匹配不需要session的回调 template <NoSessionCallback<F, S> F, typename S> decltype(auto) runWithRetry(F&& f, S&& s) { for (;;) { try { return run(std::forward<F>(f), std::forward<S>(s)); } catch (Retry&) {} } } // 重载2:匹配需要session的回调 template <SessionCallback<F, S> F, typename S> decltype(auto) runWithRetry(F&& f, S&& s) { for (;;) { try { return run(std::forward<F>(f), std::forward<S>(s)); } catch (Retry&) {} } }
两种方法都避免了重复编写重试逻辑,且无需手动实现return_type类,完全依赖C++20的编译期特性完成自动适配。
内容的提问来源于stack exchange,提问作者Hesky Fisher
相关产品推荐
相关产品推荐

