如何实现支持std::expected的通用magic_apply?C++23有现成特性吗?
C++23中简化std::expected错误检查的方案
问题背景
现有如下C++23代码:
std::expected<A, std::string> getA(const X& x); std::expected<B, std::string> getB(const Y& y); C compute_all(const A& a, const B& b);
传统的错误检查写法需要逐个判断std::expected的状态,代码冗余:
auto a_ret = getA(x); if (!a_ret) return a_ret.error(); auto b_ret = getB(y); if (!b_ret) return b_ret.error(); C final_ret = compute_all(*a_ret, *b_ret);
希望简化为类似如下的形式:
std::expected<C, std::string> final_ret = magic_apply(compute_all, getA(x), getB(y));
已有一个仅支持两个std::expected参数的magic_apply实现,但需要更通用的版本——支持变长模板,且允许向compute_all传入非std::expected类型的参数:
template<typename Func, typename A, typename B, typename Err> auto magic_apply(Func func, const std::expected<A, Err>& a, const std::expected<B, Err>& b) -> std::expected<decltype(func(a.value(), b.value())), Err> { if(!a) { return std::unexpected{ a.error() }; } if(!b) { return std::unexpected{ b.error() }; } return func(a.value(), b.value()); }
提问:C++语言中是否已有可直接实现该功能的内置特性?
回答
C23本身没有提供直接对应这种"批量解包std::expected并短路处理错误"的内置特性,但可以基于C23的现有语言特性(变长模板、折叠表达式、类型特征、完美转发等)实现通用版的magic_apply,满足你的需求:
通用版magic_apply实现
#include <expected> #include <type_traits> #include <utility> namespace detail { // 提取参数值:非expected类型直接转发,expected类型取内部值 template <typename T> constexpr decltype(auto) get_value(T&& val) noexcept { return std::forward<T>(val); } template <typename T, typename E> constexpr decltype(auto) get_value(std::expected<T, E>&& exp) { return std::move(exp).value(); } template <typename T, typename E> constexpr decltype(auto) get_value(const std::expected<T, E>& exp) { return exp.value(); } template <typename T, typename E> constexpr decltype(auto) get_value(std::expected<T, E>& exp) { return exp.value(); } // 检查所有expected参数,返回第一个错误 template <typename Err> constexpr std::expected<void, Err> check_errors() noexcept { return {}; } template <typename Err, typename First, typename... Rest> constexpr std::expected<void, Err> check_errors(const First& first, const Rest&... rest) { if constexpr (std::is_same_v<std::decay_t<First>, std::expected<typename First::value_type, Err>>) { if (!first) { return std::unexpected{first.error()}; } } return check_errors<Err>(rest...); } // 从参数列表推导错误类型(取第一个expected的错误类型) template <typename... Args> struct error_type_from_args { using type = void; }; template <typename T, typename E, typename... Rest> struct error_type_from_args<std::expected<T, E>, Rest...> { using type = E; }; template <typename T, typename E, typename... Rest> struct error_type_from_args<const std::expected<T, E>&, Rest...> { using type = E; }; template <typename T, typename E, typename... Rest> struct error_type_from_args<std::expected<T, E>&, Rest...> { using type = E; }; template <typename T, typename E, typename... Rest> struct error_type_from_args<std::expected<T, E>&&, Rest...> { using type = E; }; template <typename... Args> using error_type_from_args_t = typename error_type_from_args<Args...>::type; } template <typename Func, typename... Args> constexpr auto magic_apply(Func&& func, Args&&... args) { using ErrType = detail::error_type_from_args_t<Args...>; // 无expected参数时,直接调用函数并返回结果 if constexpr (std::is_same_v<ErrType, void>) { return std::forward<Func>(func)(std::forward<Args>(args)...); } else { // 先检查所有expected参数,遇到第一个错误立即返回 auto err_check = detail::check_errors<ErrType>(args...); if (!err_check) { return std::expected<decltype(detail::get_value(std::forward<Args>(args))...), ErrType>( std::unexpected{err_check.error()} ); } // 所有参数有效,调用函数并包装为expected返回 return std::expected{ std::forward<Func>(func)(detail::get_value(std::forward<Args>(args))...) }; } }
实现特性说明
- 变长参数支持:可接受任意数量的参数,无需限制个数
- 混合参数类型:同时支持
std::expected和普通类型参数,普通参数会直接转发给目标函数 - 短路错误处理:遍历参数时遇到第一个错误的
std::expected就立即返回该错误,不会继续处理后续参数 - 完美转发:保持参数的左值/右值属性,避免不必要的拷贝
- 自动类型推导:自动从第一个
std::expected参数推导错误类型,无需手动指定
内容的提问来源于stack exchange,提问作者Teolazza
相关产品推荐
相关产品推荐

