Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/zhb2000/maybe-extension
Functional interfaces and useful extension methods for "Maybe types" in C++ (e.g., std::optional, pointers, and smart pointers).
https://github.com/zhb2000/maybe-extension
c-plus-plus cplusplus cpp functional-programming
Last synced: 23 days ago
JSON representation
Functional interfaces and useful extension methods for "Maybe types" in C++ (e.g., std::optional, pointers, and smart pointers).
- Host: GitHub
- URL: https://github.com/zhb2000/maybe-extension
- Owner: zhb2000
- Created: 2021-12-30T11:08:06.000Z (about 3 years ago)
- Default Branch: master
- Last Pushed: 2021-12-30T13:40:55.000Z (about 3 years ago)
- Last Synced: 2024-11-06T07:12:25.549Z (2 months ago)
- Topics: c-plus-plus, cplusplus, cpp, functional-programming
- Language: C++
- Homepage:
- Size: 40 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README-zh.md
Awesome Lists containing this project
README
Maybe Extension for C++
English | 中文
# 简介
在 C++ 中通常用 `std::optional` 表示可空的值,用 `T*` 表示可空引用。然而,标准库并没有为操作这些类型提供统一的接口。Maybe Extension 为这些 C++ 中的“可空类型”提供了一组扩展方法,API 跟 Rust 的 `Option` 非常相似。鉴于 C++ 尚未为扩展方法提供语言支持,这里的“扩展方法”是通过重载 `|` 运算符实现的。`maybe | and_then(...) | map(...)` 就相当于 `maybe.and_then(...).map(...)`。
# 如何使用
语言版本:C++20这是个单头文件的库,只需要包含 [single_include/maybe_ext/maybe_ext.hpp](https://github.com/zhb2000/maybe-extension/blob/master/single_include/maybe_ext/maybe_ext.hpp) 这个头文件。
导入所有扩展方法:
```cpp
using namespace maybe_ext;
```只导入一部分方法:
```cpp
using maybe_ext::operator|,
maybe_ext::and_then,
maybe_ext::map;
```# API 列表
Monad 和 functor 操作:- `and_then`(monadic bind,又名 flatmap)
- `map`(functor map,C++23 中称为 `transform`)
- `or_else`
- `flatten`
- `filter`提取或观察所含的值:
- `deref`(使用 `*` 运算符,类似 Rust 的 `unwrap_unchecked`)
- `value_or`
- `value_or_else`
- `contains`
- `map_or`
- `map_or_else`所有权:
- `as_unowned` (Rust 里的 `as_ref` 和 `as_mut`)
- `cloned`布尔操作:
- `and_`
- `or_`
- `xor_`其他工具(在 `maybe_ext::utils` 命名空间中):
- `operator>>`
- `Convertor`
- `mut_fn`
- `const_fn`
- `rref_fn`
- `const_rref_fn`# 示例
示例代码可在 `examples` 文件夹中找到。你可以通过 [Wandbox](https://wandbox.org/permlink/XfdfY6TPn2ygyDpv) 或 [Compiler Explorer](https://godbolt.org/z/f37b8h3Y5) 在线运行代码。## 串联可能失败的操作
如果需要串联一系列可能失败的操作,那么 monadic operation 将派上用场。```cpp
// 一系列可能失败的操作
// 若该操作失败则返回 nullopt
optional peel(Food food) { ... }
optional chop(Peeled peeled) { ... }
optional cook(Chopped chopped) { ... }
``````cpp
// 若 food 为空或任意操作失败则返回 nullopt
optional process(optional food) {
return std::move(food)
| and_then(peel)
| and_then(chop)
| and_then(cook);
}
```If-else 版本:
```cpp
optional process(optional food) {
if (!food) {
return nullopt;
}
optional peeled = peel(std::move(*food));
if (!peeled) {
return nullopt;
}
optional chopped = chop(std::move(*peeled));
if (!chopped) {
return nullopt;
}
return cook(std::move(*chopped));
}
```## 可选链
在 C# 和 Javascript 中可以用 `?.` 运算符从可空对象中提取属性。虽然 C++ 里没有这样的语法糖,但是可以用 `and_then` 和 `map` 实现同样的操作:```cpp
struct User {
optional name;
};struct Post {
int id;
optional author;
};// 若未找到则返回 nullopt
optional get_post_by_id(int id) { ... }
```使用 `and_then` 和 `map` 获取帖子作者的姓名长度:
```cpp
// 若未找到则返回 nullopt
optional author_name_len(int post_id) {
return get_post_by_id(post_id)
| and_then(&Post::author)
| and_then(&User::name)
| map(&string::length);
}
```If-else 版本:
```cpp
optional author_name_len(int post_id) {
optional post = get_post_by_id(post_id);
if (post && post->author && post->author->name) {
return post->author->name->length();
}
return nullopt;
}
```## 指针类型的例子
除 `std::optional` 外,指针类型也可以使用这些扩展方法。下面这个例子使用 `map_or` 计算二叉树的高度。```cpp
struct Node {
Node *lch = nullptr;
Node *rch = nullptr;
int height() const {
int lh = lch | map_or(0, &Node::height);
int rh = rch | map_or(0, &Node::height);
return 1 + std::max(lh, rh);
}
};
```# 作为“语法糖”的成员指针
对于像 `and_then` 和 `map` 这样的方法,不仅可以传入传统的可调用对象,还可以传入成员指针。这旨在简化代码。```cpp
struct User {
string name;
optional age;
};optional maybe_user = User{.name = "Bob", .age = 20};
// 使用成员对象指针 &User::name
string *maybe_name = maybe_user | map(&User::name);
// 使用成员对象指针 &User::age
int *maybe_age = maybe_user | and_then(&User::age);
// 使用成员函数指针 &string::length
optional maybe_len = maybe_name | map(&string::length);assert(maybe_name && *maybe_name == "Bob");
assert(maybe_age && *maybe_age == 20);
assert(maybe_len == optional(3));
```等价的 lambda 版本,如你所见,C++ 的 lambda 表达式非常非常啰嗦:
```cpp
string *maybe_name = maybe_user | map([](User &user) -> string& { return user.name; })
int *maybe_age = maybe_user | and_then([](User &user) { return as_unowned(user.age); })
optional maybe_len = maybe_name | map([](string &s) { return s.length(); });
```在 `and_then` 和 `map` 中使用成员对象指针时,请注意临时对象的生存期,以避免产生悬空指针。
以下代码会造成未定义行为,`maybe_name` 成了悬空指针,因为 `make_user()` 的返回值是一个临时对象,而临时对象会在语句结束时被析构。
```cpp
optional make_user() {
return User{.name = "Bob", .age = 20};
}string *maybe_name = make_user() | map(&User::name); // 错误!
```在末尾加一个 `cloned()` 就能修复这个问题。请注意 `cloned()` 产出了一个 `optional`(可空的值)而不是 `string *`(可空引用)。这意味着变量 `maybe_name` 现在拥有这个 string 的所有权。
```cpp
optional maybe_name = make_user()
| map(&User::name)
| cloned(); // 正确
```# 附带工具
## `>>` 运算符
`>>` 运算符和 `and_then` 是等价的。```cpp
using maybe_ext::utils::operator>>;optional sq(int x) { return x * x; }
optional nope(int) { return nullopt; }assert_eq(optional(2) >> sq >> sq, optional(16));
assert_eq(optional(2) >> sq >> nope, nullopt);
assert_eq(optional(2) >> nope >> sq, nullopt);
assert_eq(optional() >> sq >> sq, nullopt);
```注:C++ 可以重载 `>>=` 运算符,但 `>>=` 是右结合的,这样一来代码就得写成 `(optional(2) >>= sq) >>= nope`。为了少写括号,这里重载的是 `>>` 运算符。
## Convertor
下述示例使用一个 `Convertor` 把字符串长度转换为 `int` 类型。```cpp
using maybe_ext::utils::Convertor;
optional maybe_string = "whu";
optional maybe_len = maybe_string
| map(&string::length)
| map(Convertor()); // string::size_type -> int
assert(maybe_len == optional(3));
```## 重载方法选择器
使用成员函数指针时,可能会因为存在多个重载函数而出现编译错误。```cpp
struct Object {
string method() { return "method()"; }
string method() const { return "method() const"; }
};optional maybe = Object();
optional m = maybe | map(&Object::method);
// 编译错误,因为编译器不知道该选哪个 `method`
```辅助函数 `mut_fn` 可以选中 `method` 的非 const 重载。
```cpp
using maybe_ext::utils::mut_fn;
optional m = maybe | map(mut_fn(&Object::method));
assert(m == optional("method()"));
```使用 lambda 也不失为一种解决办法。
```cpp
optional m = maybe | map([](Object &x) { return x.method(); });
assert(m == optional("method()"));
```# 参见
Rust 中的 `Option` 类型:[Module std::option](https://doc.rust-lang.org/std/option/index.html), [Option in std::option](https://doc.rust-lang.org/std/option/enum.Option.html)C++23 为 [std::optional](https://en.cppreference.com/w/cpp/utility/optional) 增添了 [and_then](https://en.cppreference.com/w/cpp/utility/optional/and_then)、[transform](https://en.cppreference.com/w/cpp/utility/optional/transform) 和 [or_else](https://en.cppreference.com/w/cpp/utility/optional/or_else) 方法。相关提案:[p0798R6 Monadic operations for std::optional](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0798r6.html)。