文章目录
- ⭐前言
- ⭐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