{"id":18447021,"url":"https://github.com/zhb2000/maybe-extension","last_synced_at":"2025-04-15T02:45:44.818Z","repository":{"id":46138728,"uuid":"443050542","full_name":"zhb2000/maybe-extension","owner":"zhb2000","description":"Functional interfaces and useful extension methods for \"Maybe types\" in C++ (e.g., std::optional, pointers, and smart pointers).","archived":false,"fork":false,"pushed_at":"2021-12-30T13:40:55.000Z","size":41,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-15T02:45:41.295Z","etag":null,"topics":["c-plus-plus","cplusplus","cpp","functional-programming"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zhb2000.png","metadata":{"files":{"readme":"README-zh.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-12-30T11:08:06.000Z","updated_at":"2022-07-18T08:47:55.000Z","dependencies_parsed_at":"2022-09-01T06:02:18.902Z","dependency_job_id":null,"html_url":"https://github.com/zhb2000/maybe-extension","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhb2000%2Fmaybe-extension","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhb2000%2Fmaybe-extension/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhb2000%2Fmaybe-extension/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhb2000%2Fmaybe-extension/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zhb2000","download_url":"https://codeload.github.com/zhb2000/maybe-extension/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248997087,"owners_count":21195797,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-plus-plus","cplusplus","cpp","functional-programming"],"created_at":"2024-11-06T07:11:42.487Z","updated_at":"2025-04-15T02:45:44.800Z","avatar_url":"https://github.com/zhb2000.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eMaybe Extension for C++\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\u003ca href=\"./README.md\"\u003eEnglish\u003c/a\u003e | \u003cb\u003e中文\u003c/b\u003e\u003c/p\u003e\n\n# 简介\n在 C++ 中通常用 `std::optional\u003cT\u003e` 表示可空的值，用 `T*` 表示可空引用。然而，标准库并没有为操作这些类型提供统一的接口。Maybe Extension 为这些 C++ 中的“可空类型”提供了一组扩展方法，API 跟 Rust 的 `Option` 非常相似。\n\n鉴于 C++ 尚未为扩展方法提供语言支持，这里的“扩展方法”是通过重载 `|` 运算符实现的。`maybe | and_then(...) | map(...)` 就相当于 `maybe.and_then(...).map(...)`。\n\n# 如何使用\n语言版本：C++20\n\n这是个单头文件的库，只需要包含 [single_include/maybe_ext/maybe_ext.hpp](https://github.com/zhb2000/maybe-extension/blob/master/single_include/maybe_ext/maybe_ext.hpp) 这个头文件。\n\n导入所有扩展方法：\n\n```cpp\nusing namespace maybe_ext;\n```\n\n只导入一部分方法：\n\n```cpp\nusing maybe_ext::operator|,\n      maybe_ext::and_then,\n      maybe_ext::map;\n```\n\n# API 列表\nMonad 和 functor 操作：\n\n- `and_then`（monadic bind，又名 flatmap）\n- `map`（functor map，C++23 中称为 `transform`）\n- `or_else`\n- `flatten`\n- `filter`\n\n提取或观察所含的值：\n\n- `deref`（使用 `*` 运算符，类似 Rust 的 `unwrap_unchecked`）\n- `value_or`\n- `value_or_else`\n- `contains`\n- `map_or`\n- `map_or_else`\n\n所有权：\n\n- `as_unowned` (Rust 里的 `as_ref` 和 `as_mut`)\n- `cloned`\n\n布尔操作：\n\n- `and_`\n- `or_`\n- `xor_`\n\n其他工具（在 `maybe_ext::utils` 命名空间中）：\n\n- `operator\u003e\u003e`\n- `Convertor`\n- `mut_fn`\n- `const_fn`\n- `rref_fn`\n- `const_rref_fn`\n\n# 示例\n示例代码可在 `examples` 文件夹中找到。你可以通过 [Wandbox](https://wandbox.org/permlink/XfdfY6TPn2ygyDpv) 或 [Compiler Explorer](https://godbolt.org/z/f37b8h3Y5) 在线运行代码。\n\n## 串联可能失败的操作\n如果需要串联一系列可能失败的操作，那么 monadic operation 将派上用场。\n\n```cpp\n// 一系列可能失败的操作\n// 若该操作失败则返回 nullopt\noptional\u003cPeeled\u003e peel(Food food) { ... }\noptional\u003cChopped\u003e chop(Peeled peeled) { ... }\noptional\u003cCooked\u003e cook(Chopped chopped) { ... }\n```\n\n```cpp\n// 若 food 为空或任意操作失败则返回 nullopt\noptional\u003cCooked\u003e process(optional\u003cFood\u003e food) {\n    return std::move(food)\n           | and_then(peel)\n           | and_then(chop)\n           | and_then(cook);\n}\n```\n\nIf-else 版本：\n\n```cpp\noptional\u003cCooked\u003e process(optional\u003cFood\u003e food) {\n    if (!food) {\n        return nullopt;\n    }\n    optional\u003cPeeled\u003e peeled = peel(std::move(*food));\n    if (!peeled) {\n        return nullopt;\n    }\n    optional\u003cChopped\u003e chopped = chop(std::move(*peeled));\n    if (!chopped) {\n        return nullopt;\n    }\n    return cook(std::move(*chopped));\n}\n```\n\n## 可选链\n在 C# 和 Javascript 中可以用 `?.` 运算符从可空对象中提取属性。虽然 C++ 里没有这样的语法糖，但是可以用 `and_then` 和 `map` 实现同样的操作：\n\n```cpp\nstruct User {\n    optional\u003cstring\u003e name;\n};\n\nstruct Post {\n    int id;\n    optional\u003cUser\u003e author;\n};\n\n// 若未找到则返回 nullopt\noptional\u003cPost\u003e get_post_by_id(int id) { ... }\n```\n\n使用 `and_then` 和 `map` 获取帖子作者的姓名长度：\n\n```cpp\n// 若未找到则返回 nullopt\noptional\u003csize_t\u003e author_name_len(int post_id) {\n    return get_post_by_id(post_id)\n           | and_then(\u0026Post::author)\n           | and_then(\u0026User::name)\n           | map(\u0026string::length);\n}\n```\n\nIf-else 版本：\n\n```cpp\noptional\u003csize_t\u003e author_name_len(int post_id) {\n    optional\u003cPost\u003e post = get_post_by_id(post_id);\n    if (post \u0026\u0026 post-\u003eauthor \u0026\u0026 post-\u003eauthor-\u003ename) {\n        return post-\u003eauthor-\u003ename-\u003elength();\n    }\n    return nullopt;\n}\n```\n\n## 指针类型的例子\n除 `std::optional` 外，指针类型也可以使用这些扩展方法。下面这个例子使用 `map_or` 计算二叉树的高度。\n\n```cpp\nstruct Node {\n    Node *lch = nullptr;\n    Node *rch = nullptr;\n    int height() const {\n        int lh = lch | map_or(0, \u0026Node::height);\n        int rh = rch | map_or(0, \u0026Node::height);\n        return 1 + std::max(lh, rh);\n    }\n};\n```\n\n# 作为“语法糖”的成员指针\n对于像 `and_then` 和 `map` 这样的方法，不仅可以传入传统的可调用对象，还可以传入成员指针。这旨在简化代码。\n\n```cpp\nstruct User {\n    string name;\n    optional\u003cint\u003e age;\n};\n\noptional\u003cUser\u003e maybe_user = User{.name = \"Bob\", .age = 20};\n\n// 使用成员对象指针 \u0026User::name\nstring *maybe_name = maybe_user | map(\u0026User::name);\n// 使用成员对象指针 \u0026User::age\nint *maybe_age = maybe_user | and_then(\u0026User::age);\n// 使用成员函数指针 \u0026string::length\noptional\u003csize_t\u003e maybe_len = maybe_name | map(\u0026string::length);\n\nassert(maybe_name \u0026\u0026 *maybe_name == \"Bob\");\nassert(maybe_age \u0026\u0026 *maybe_age == 20);\nassert(maybe_len == optional(3));\n```\n\n等价的 lambda 版本，如你所见，C++ 的 lambda 表达式非常非常啰嗦：\n\n```cpp\nstring *maybe_name = maybe_user | map([](User \u0026user) -\u003e string\u0026 { return user.name; })\nint *maybe_age = maybe_user | and_then([](User \u0026user) { return as_unowned(user.age); })\noptional\u003csize_t\u003e maybe_len = maybe_name | map([](string \u0026s) { return s.length(); });\n```\n\n在 `and_then` 和 `map` 中使用成员对象指针时，请注意临时对象的生存期，以避免产生悬空指针。\n\n以下代码会造成未定义行为，`maybe_name` 成了悬空指针，因为 `make_user()` 的返回值是一个临时对象，而临时对象会在语句结束时被析构。\n\n```cpp\noptional\u003cUser\u003e make_user() {\n    return User{.name = \"Bob\", .age = 20};\n}\n\nstring *maybe_name = make_user() | map(\u0026User::name); // 错误！\n```\n\n在末尾加一个 `cloned()` 就能修复这个问题。请注意 `cloned()` 产出了一个 `optional\u003cstring\u003e`（可空的值）而不是 `string *`（可空引用）。这意味着变量 `maybe_name` 现在拥有这个 string 的所有权。\n\n```cpp\noptional\u003cstring\u003e maybe_name = make_user()\n                              | map(\u0026User::name)\n                              | cloned(); // 正确\n```\n\n# 附带工具\n## `\u003e\u003e` 运算符\n`\u003e\u003e` 运算符和 `and_then` 是等价的。\n\n```cpp\nusing maybe_ext::utils::operator\u003e\u003e;\n\noptional\u003cint\u003e sq(int x) { return x * x; }\noptional\u003cint\u003e nope(int) { return nullopt; }\n\nassert_eq(optional(2) \u003e\u003e sq \u003e\u003e sq, optional(16));\nassert_eq(optional(2) \u003e\u003e sq \u003e\u003e nope, nullopt);\nassert_eq(optional(2) \u003e\u003e nope \u003e\u003e sq, nullopt);\nassert_eq(optional\u003cint\u003e() \u003e\u003e sq \u003e\u003e sq, nullopt);\n```\n\n注：C++ 可以重载 `\u003e\u003e=` 运算符，但 `\u003e\u003e=` 是右结合的，这样一来代码就得写成 `(optional(2) \u003e\u003e= sq) \u003e\u003e= nope`。为了少写括号，这里重载的是 `\u003e\u003e` 运算符。\n\n## Convertor\n下述示例使用一个 `Convertor\u003cint\u003e` 把字符串长度转换为 `int` 类型。\n\n```cpp\nusing maybe_ext::utils::Convertor;\noptional\u003cstring\u003e maybe_string = \"whu\";\noptional\u003cint\u003e maybe_len = maybe_string\n                          | map(\u0026string::length)\n                          | map(Convertor\u003cint\u003e()); // string::size_type -\u003e int\nassert(maybe_len == optional(3));\n```\n\n## 重载方法选择器\n使用成员函数指针时，可能会因为存在多个重载函数而出现编译错误。\n\n```cpp\nstruct Object {\n    string method() { return \"method()\"; }\n    string method() const { return \"method() const\"; }\n};\n\noptional\u003cObject\u003e maybe = Object();\noptional\u003cstring\u003e m = maybe | map(\u0026Object::method);\n// 编译错误，因为编译器不知道该选哪个 `method`\n```\n\n辅助函数 `mut_fn` 可以选中 `method` 的非 const 重载。\n\n```cpp\nusing maybe_ext::utils::mut_fn;\noptional\u003cstring\u003e m = maybe | map(mut_fn(\u0026Object::method));\nassert(m == optional\u003cstring\u003e(\"method()\"));\n```\n\n使用 lambda 也不失为一种解决办法。\n\n```cpp\noptional\u003cstring\u003e m = maybe | map([](Object \u0026x) { return x.method(); });\nassert(m == optional\u003cstring\u003e(\"method()\"));\n```\n\n# 参见\nRust 中的 `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)\n\nC++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)。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhb2000%2Fmaybe-extension","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzhb2000%2Fmaybe-extension","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhb2000%2Fmaybe-extension/lists"}