You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何让std::optional<T>::value_or简洁地作用于T的成员变量?

针对std::optional成员变量的value_or式优雅实现

你想要的是给std::optional<T>里的T类型成员变量提供类似value_or的默认值机制,而且得支持在类初始化列表里使用,不能用结构化绑定——这个需求很实际,尤其是在需要简洁初始化的场景里。

先看你现有的memb_or模板,思路是对的,但可以结合标准库的工具让它更规范、更符合C++的风格,同时补上你担心的类型约束和返回值处理。

基于标准库的优化实现

我们可以利用std::invoke(你已经用到了),再结合类型特征、C20概念(或C17的静态断言)来完善类型约束,同时保证返回值的正确性:

#include <iostream>
#include <optional>
#include <functional>
#include <type_traits>

struct S{ int b; int e; };

// C++20+ 版本,用概念约束确保成员访问器合法
template<typename T, typename M, typename V>
requires std::invocable<M, const T&>
constexpr decltype(auto) memb_or(const std::optional<T>& opt, M&& member_accessor, const V& default_val)
{
    if (opt)
    {
        return std::invoke(std::forward<M>(member_accessor), *opt);
    }
    else
    {
        // 确保默认值和成员访问结果的类型兼容
        using ReturnType = std::decay_t<decltype(std::invoke(std::forward<M>(member_accessor), std::declval<const T&>()))>;
        return static_cast<ReturnType>(default_val);
    }
}

// C++17兼容版本,用static_assert替代概念做约束
template<typename T, typename M, typename V>
constexpr decltype(auto) memb_or_cpp17(const std::optional<T>& opt, M&& member_accessor, const V& default_val)
{
    static_assert(std::is_invocable_v<M, const T&>, "Member accessor must be callable with const T&");
    if (opt)
    {
        return std::invoke(std::forward<M>(member_accessor), *opt);
    }
    else
    {
        using ReturnType = std::decay_t<decltype(std::invoke(std::forward<M>(member_accessor), std::declval<const T&>()))>;
        return static_cast<ReturnType>(default_val);
    }
}

// 测试初始化列表场景
struct TestClass {
    int begin;
    int end;

    TestClass(const std::optional<S>& maybe_s)
        : begin(memb_or(maybe_s, &S::b, 0)),
          end(memb_or(maybe_s, &S::e, 100)) {}
};

void fn(const std::optional<S>& maybe_s){
    // 替代原有的三元表达式逻辑
    int begin = memb_or(maybe_s, &S::b, 0);
    int end = memb_or(maybe_s, &S::e, 100);
    std::cout << begin << " " << end << std::endl;
}

int main() {
    std::optional<S> empty_opt;
    fn(empty_opt); // 输出 0 100
    std::optional<S> active_opt{S{11, 47}};
    fn(active_opt); // 输出 11 47

    // 验证初始化列表可用性
    TestClass tc_empty(empty_opt);
    std::cout << tc_empty.begin << " " << tc_empty.end << std::endl; // 0 100
    TestClass tc_active(active_opt);
    std::cout << tc_active.begin << " " << tc_active.end << std::endl; // 11 47
}

这个方案的优势

  • 标准库友好:完全依赖std::invoke、类型特征等标准组件,熟悉标准库的开发者一眼就能理解,不需要额外学习自定义逻辑。
  • 类型安全:通过C20概念(或C17的static_assert)确保传入的成员访问器可以正确作用于T类型,提前拦截类型错误。
  • 返回值兼容:主动将默认值转换为和成员访问结果一致的类型,避免隐式转换带来的潜在问题。
  • 初始化列表兼容:纯函数调用的形式,没有任何语句逻辑,完全可以在类的初始化列表里使用,完美满足你的核心需求。
  • 灵活性高:不仅支持成员指针(比如&S::b),还支持lambda或其他可调用对象,比如你可以直接对成员做转换:memb_or(maybe_s, [](const S& s){ return s.b * 2; }, 0)

关于性能的说明

这个实现不会有额外性能开销:std::invoke对成员指针的调用是完全constexpr且可被编译器内联的,分支逻辑也非常简单,编译器会优化成和你手动写三元表达式几乎一致的机器码,不用担心性能问题。

内容的提问来源于stack exchange,提问作者NoSenseEtAl

火山引擎 最新活动