当前位置: 首页 > article >正文

(C++23) expected 基础使用

文章目录

  • ⭐前言
  • ⭐expected
    • 🎛️基础使用
    • 🎛️单子操作 (Monadic operations)
      • 🎚️and_then & or_else
      • 🎚️transform & transform_error
  • ⭐END
    • 🌟跋
    • 🌟交流方式

⭐前言

在 C++17 中,提出了 optional 的可选项类型。但是该类型仅能表示二元状态,true | false 但在实际业务常见成功与否可以只用一个flag表示,但失败的原因却多种多样,需要以一种更丰富的形式表示。

因此在 C++23 中正式规范了std::expected,操作方式与原先的std::optional相似,又扩展了一些统一的处理方案。

同时这些方法在 C++23 中也为 std::optional 提供。

⭐expected

下面以一个获取星期几的字符串的demo为例子来表示。

🎛️基础使用

操作以使用 std::optional 一致,但这里多了一项 error 的选项。

#include <ctime>
#include <expected>
#include <iostream>
#include <string>
#include <typeinfo>

enum struct ParseError : int {
    Below,  // (<0)
    Above   // (>6)
};

std::expected<std::string, ParseError> get_weekDay(int d) {
    if (d < 0) {
        return std::unexpected(ParseError::Below);
    } else if (d > 6) {
        return std::unexpected(ParseError::Above);
    }

    // C++20 指定初始化
    std::tm t{.tm_wday = d};
    char    buffer[16];
    std::strftime(buffer, sizeof(buffer), "%A", &t);

    return std::string(buffer);
}

void test(int d) {
    auto ex = get_weekDay(d);
    if (ex.has_value()) {
        std::cout << ex.value() << std::endl;
    } else {
        if (ex.error() == ParseError::Below) {
            std::cout << "ParseError::Below" << std::endl;
        } else if (ex.error() == ParseError::Above) {
            std::cout << "ParseError::Above" << std::endl;
        } else {
            std::cout << ex.value_or("ParseError") << std::endl;
        }
    }
}

int main() {
    test(-1);
    test(0);
    test(114514);
}

🎛️单子操作 (Monadic operations)

下面仅对上面 demo 的 void test(int) 函数进行修改

一般来说 Monadic operations 会被翻译成一元操作,或者单目操作。不知道为什么std::expected - cppreference.com 这里翻译成单子操作。

🎚️and_then & or_else

and_then() 如果 *this:

  • 表示预期值,那么调用 f 并返回它的结果。

  • 否则返回一个包含非预期值的 std::expected 对象,以 *this 的非预期值初始化该值。

or_else 如果 *this:

  • 包含非预期值,那么以 *this 的非预期值作为实参调用 f 并返回它的结果。

  • 否则返回一个表示预期值的 std::expected 对象。

因此两者的使用非常灵活,使用得当可以非常遍历的进行链式编程。

这个写法让我回想起以前写 javascript 的时候。

void test(int d) {
    auto ex = get_weekDay(d);

    ex
        /**
         * 若存在预期值则返回给定的函数在其上的结果,否则返回 expected 本身
         */
        .and_then([](const auto& e) -> std::expected<std::string, ParseError> {
            std::cout << "[and_then] Type:" << typeid(e).name() << std::endl;
            std::cout << e << std::endl;
            return {e};
        })
        /**
         * 若 expected 含有预期值则返回其自身,否则返回给定的函数在非预期值上的结果
         */
        .or_else([](const auto& e) -> std::expected<std::string, ParseError> {
            std::cout << "[or_else] Type:" << typeid(e).name() << std::endl;

            if (e == ParseError::Below) {
                std::cout << "ParseError::Below" << std::endl;
            } else if (e == ParseError::Above) {
                std::cout << "ParseError::Above" << std::endl;
            } else {
                std::cout << "ParseError" << std::endl;
            }
            return std::unexpected{e};
        });
}

mingw-gcc 运行效果

[or_else] Type:10ParseError
ParseError::Below
[and_then] Type:NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Sunday
[or_else] Type:10ParseError
ParseError::Above

🎚️transform & transform_error

transform() 如果 *this:

  • 表示预期值,那么调用 f 并返回一个包含预期值的 std::expected 对象,以 f 的结果初始化该值(或在结果类型是 void 时值初始化)。
  • 否则返回一个包含非预期值的 std::expected 对象,以 *this 的非预期值初始化该值。

transform_error() 如果 *this:

  • 包含非预期值,那么以 *this 的非预期值作为实参调用 f 并返回一个包含预期值的 std::expected 对象,以 f 的结果初始化该值。
  • 否则返回一个表示预期值的 std::expected 对象。

这两个基本可以被and_then & or_else取代。

主要区别在于,在成功调用时能保证返回一个std::expected,且依赖于 f 的返回值。

这是少有的在 C++ 中可以天然的允许链式编程的函数。

void test(int d) {
    auto ex = get_weekDay(d);

    ex
        /**
         * 若存在预期值则返回含有变换后的预期值的 expected,否则返回 expected 本身
         */
        .transform([](const auto& e) -> std::expected<std::string, ParseError> {
            std::cout << "[transform] Type:" << typeid(e).name() << std::endl;
            std::cout << e << std::endl;
            return {e};
        })
        /**
         * 若含有预期值则返回 expected 本身,否则返回含有变换后非预期值的 expected
         */
        .transform_error([](const auto& e) -> std::expected<std::string, ParseError> {
            std::cout << "[transform_error] Type:" << typeid(e).name() << std::endl;

            if (e == ParseError::Below) {
                std::cout << "ParseError::Below" << std::endl;
            } else if (e == ParseError::Above) {
                std::cout << "ParseError::Above" << std::endl;
            } else {
                std::cout << "ParseError" << std::endl;
            }
            return std::unexpected{e};
        });
}

mingw-gcc 运行效果

[transform_error] Type:10ParseError
ParseError::Below
[transform] Type:NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Sunday
[transform_error] Type:10ParseError
ParseError::Above

⭐END

最后,如果像更深刻的体会这里的链式效果,建议将上面代码的 and_then & or_else 以相反的顺序调用一遍,会有不一样的效果。

这样可以帮助更好的理解调用的逻辑和设计出更好的代码。

🌟跋

ref:

标准库标头 expected

相关类型:

  • (C++17) any的使用与简单实现
  • (C++17) optional的使用
  • (C++17) variant的使用与union对比
  • (C++17) optional 的 3 种用法

🌟交流方式

⭐交流方式⭐ |C/C++|算法|设计模式|软件架构-CSDN社区

关注我,学习更多C/C++,python,算法,软件工程,计算机知识

B站:

👨‍💻主页:天赐细莲 bilibili


http://www.kler.cn/a/315178.html

相关文章:

  • C语言 | Leetcode C语言题解之第557题反转字符串中的单词III
  • 【STM32F1】——无线收发模块RF200与串口通信
  • MySQL技巧之跨服务器数据查询:进阶篇-从A数据库复制到B数据库的表中
  • 前端神经网络入门(三):深度学习与机器学习的关系、区别及核心理论支撑 - 以Brain.js示例
  • 2024版本IDEA创建Sprintboot项目下载依赖缓慢
  • ML 系列: 第 23 节 — 离散概率分布 (多项式分布)
  • hive-拉链表
  • 代码随想录算法训练营|151.翻转字符串里的单词 、卡码网:55.右旋转字符串
  • 分布式Redis(14)哈希槽
  • 深入理解Go并发编程:避免Goroutine泄漏与错误处理
  • C++_数据封装详解
  • 综述论文“Towards Personalized Federated Learning”分享
  • 研究生第一次刷力扣day1
  • 认识结构体
  • Docker笔记-Docker Dockerfile
  • 语言模型的在线策略提炼:从自我错误中学习
  • Redis数据结构之set
  • 音视频入门基础:AAC专题(8)——FFmpeg源码中计算AAC裸流AVStream的time_base的实现
  • Qt:静态局部变量实现单例(附带单例使用和内存管理)
  • socket是什么?为什么要用socket?
  • Python 实现图形学几何变换算法
  • 【农信网-注册/登录安全分析报告】
  • (done) 声音信号处理基础知识(5) (Types of Audio Features for Machine Learning)
  • 【Pycharm】Pycharm创建Django提示pip版本需要升级
  • WebLogic 靶场攻略
  • JavaEE: 深入探索TCP网络编程的奇妙世界(二)