news 2026/10/19 3:35:54

C++23 核心新特性:std::expected、std::print、deducing this、monadic optional

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++23 核心新特性:std::expected、std::print、deducing this、monadic optional

C++23 是一版「补齐短板」的标准:它没有引入全新的心智模型,而是把十几年来大家反复手写的模式收进标准库。这篇挑四个最实用的讲清楚:std::expected(值或错误)、std::print(类型安全输出)、deducing this(显式对象参数)、monadic optional(可空值链式处理)。它们都需要 C++23 编译器;其中 deducing this 在 gcc 14 之后才完整支持,正文里会单独标版本。

1. 引子:错误处理的两难

写一个「解析用户输入成整数」的函数,返回值放不下「出错了」这个信息。三种老办法各有代价:返回int出错返回 0(分不清「合法的 0」和「出错的 0」)、抛异常(错误是罕见路径时太重)、返回std::optional<int>(能表示「没有值」,但说不出为什么没有)。std::expected<T, E>就是冲着第三种的缺口来的:它要么持有T(值),要么持有E(错误),两个通道都显式存在。

std::expected<int, std::string> ┌──────────────────────────────────┐ │ 有值(has_value() == true) │──── value() -> int │ or │ │ 有错(has_value() == false) │──── error() -> std::string └──────────────────────────────────┘ ↑ 两种状态,占一个对象

官方文档:std::expected — cppreference

下面用「除零」这个最经典的场景跑一遍:除数合法返回商,除数为零返回一句错误描述。

// verify: std=c++23#include<expected>#include<iostream>#include<string>std::expected<int,std::string>divide(inta,intb){if(b==0)returnstd::unexpected{"divide by zero"};returna/b;}intmain(){autook=divide(10,2);autobad=divide(10,0);std::cout<<"ok.has_value()="<<ok.has_value()<<" value="<<ok.value()<<'\n';std::cout<<"bad.has_value()="<<bad.has_value()<<" error="<<bad.error()<<'\n';std::cout<<"bad.value_or(-1)="<<bad.value_or(-1)<<'\n';}
ok.has_value()=1 value=5 bad.has_value()=0 error=divide by zero bad.value_or(-1)=-1

std::unexpected{"divide by zero"}是构造错误态的方式;读值时用value()(错误态会抛std::bad_expected_access)或value_or(-1)(错误态返回兜底值)。它和optional的差别,以及和各错误策略的取舍,见下表。

策略能表达「为什么错」错误是否强制处理高频可预期错误的代价适用
返回错误码 / 哨兵值否(或靠约定)否,容易漏判低C ABI、性能敏感的内层循环
抛异常是(异常类型)是(不 catch 就崩)高(栈展开)真正的「非正常路径」
std::optional<T>否否低只需要「有没有」
std::expected<T,E>是否(但错误和值都在类型里,显式)低库边界、可预期失败

expected适合「失败是可预期的、调用方必须知道」的场景——它把「值 or 错误」写进类型签名,编译器帮你记得处理。

2. std::print:类型安全、格式化的输出

std::cout << "x = " << x << " and y = " << y << '\n'这种写法,把格式和变量混在一串<<里,既啰嗦又难本地化。C++23 引入std::print/std::println,用法和std::format一致:{}是占位符,{:.3f}是格式化规格。

// verify: std=c++23#include<print>intmain(){std::print("{} + {} = {}\n",2,3,2+3);std::println("pi = {:.3f}",3.1415926);std::println("{}","print 不刷缓冲,比 endl 快");}
2 + 3 = 5 pi = 3.142 print 不刷缓冲,比 endl 快

官方文档:std::print / std::println — cppreference

注意std::print没有std::endl那种「顺手刷缓冲」的副作用。这里面的差异是个性能考点:std::endl会强制 flush,而print只负责写入,刷新交给系统按需完成。格式字符串写错(比如{}数量对不上参数)在 C++23 里编译期就能报错,这是 iostream 做不到的。

3. deducing this:显式对象参数

传统成员函数里,this是隐式的,你拿不到它的类型,也没法在成员函数上做「按值 vs 按引用」的分派。deducing this(显式对象参数,explicit object parameter)把this变成第一个显式参数,写this auto const& self或this Point& self。

// verify: std=c++23// verify: compiler=gcc-head#include<iostream>structPoint{intx{1},y{2};autonorm(thisautoconst&self){returnself.x*self.x+self.y*self.y;}int&pick(thisPoint&self,booluseX){returnuseX?self.x:self.y;}};intmain(){Point p;std::cout<<"norm="<<p.norm()<<'\n';p.pick(true)=10;std::cout<<"x="<<p.x<<" y="<<p.y<<'\n';}
norm=5 x=10 y=2

官方文档:Member functions — cppreference(其中「Explicit object parameter」一节)

它最大的价值是免掉 CRTP(curiously recurring template pattern,奇异递归模板模式)。以前想在基类里拿到派生类类型,得写template<class Derived> struct Base { ... };再struct D : Base<D> {};现在一个this auto&& self就够了,调用时self会自动推导成实际对象类型。

写法拿到派生类类型的方式样板量
传统成员函数拿不到,this固定为当前类指针无
CRTPstatic_cast<Derived&>(*this)多(模板基类 + 继承声明)
deducing thisthis auto&& self自动推导少

(deducing this 需要 gcc 14+ 或 clang 18+,所以这个示例指定了compiler=gcc-head,其余示例用默认的 clang-17 即可。)

4. monadic optional:可空值的链式处理

处理「可能没有值」时,最常见的写法是一串if (opt) { ... } else { ... }。C++23 给std::optional补上 monadic(单子式)操作:and_then(有值则继续、返回新 optional)、transform(有值则映射、原地包裹)、or_else(无值则走兜底)。

// verify: std=c++23#include<iostream>#include<optional>#include<string>std::optional<int>toInt(conststd::string&s){try{returnstd::stoi(s);}catch(...){returnstd::nullopt;}}intmain(){autor=std::optional<std::string>{"42"}.and_then(toInt).transform([](intx){returnx+1;}).or_else([]{returnstd::optional<int>{0};});std::cout<<"pipeline(\"42\") = "<<*r<<'\n';autor2=std::optional<std::string>{"abc"}.and_then(toInt).transform([](intx){returnx+1;}).or_else([]{returnstd::optional<int>{0};});std::cout<<"pipeline(\"abc\") = "<<*r2<<'\n';}
pipeline("42") = 43 pipeline("abc") = 0

官方文档:std::optional::and_then — cppreference

整个「字符串 → 整数 → 加一」的流水线写成一串,空值在任意一步短路、最后用or_else收口,不再需要嵌套if。expected也有同样的一套(and_then/transform/or_else/transform_error),所以学了 optional 的这套,expected 直接复用。

操作有值时无值时返回类型
transform(f)f(v)包进 optional保持空optional<decltype(f(v))>
and_then(f)f(v)(f 自己返回 optional)保持空optional<...>(f 的返回)
or_else(f)保持原值f()(f 返回 optional)与原 optional 同类型

5. 完整示例:四个特性一起用

把一个「读取配置 → 解析整数 → 打印结果」的流程串起来,覆盖这篇全部要点。

// verify: std=c++23#include<expected>#include<optional>#include<print>#include<string>std::optional<int>parse(conststd::string&s){try{returnstd::stoi(s);}catch(...){returnstd::nullopt;}}intmain(){// optional 链:解析失败兜底 8080autoport=parse("9090").and_then([](intx){returnstd::optional<int>{x};}).or_else([]{returnstd::optional<int>{8080};});// expected 链:范围校验,非法走错误通道autocheck=[](intp)->std::expected<int,std::string>{if(p<1||p>65535){returnstd::expected<int,std::string>{std::unexpected{"port out of range"}};}returnstd::expected<int,std::string>{p};};autoresult=std::expected<int,std::string>{*port}.and_then(check);autobad=std::expected<int,std::string>{99999}.and_then(check);std::println("port = {}",result.value());std::println("bad has_error={} err={}",bad.has_value(),bad.error());}
port = 9090 bad has_error=false err=port out of range

两段链各司其职:optional 的and_then/or_else处理「有没有值」,expected 的and_then处理「值合不合法」。注意 optional 的and_then要求返回 optional、expected 的and_then要求返回 expected,两者不能混用,这也是类型系统在帮你把「可空」和「可错」这两件事分开。

6. 延伸阅读

  • std::expected — cppreference:值或错误的完整接口,含 monadic 操作。
  • std::print — cppreference:格式字符串语法与std::format同源。
  • Member functions (deducing this) — cppreference:显式对象参数的语法与推导规则。
  • std::optional::and_then — cppreference:monadic optional 的入口,同页能跳到transform/or_else。

本知识库内的相关篇目:

  • 《C++20 升 C++23 迁移清单:开关、破坏性改动与收益》 —— 从 C++20 升到 C++23 是「低风险、有正收益」的一次迁移
  • 《前瞻:std::generator、reflection 与 C++ 的未来方向》 —— C++23 之后,C++26 正在路上
  • 《C++ 错误处理策略:异常 vs 错误码 vs expected 的真实取舍》 —— 函数失败到底该抛异常、返回错误码顺带把工程与架构这条线也铺一下。

7. 一句话总结

std::expected补上「值 or 错误」的缺口,std::print让输出类型安全又简洁,deducing this 消灭 CRTP 样板(需 gcc 14+),monadic optional 让可空值能像流水线一样链式处理——这四个是 C++23 里最值得先上手、也最能立刻替换掉手写样板的一组特性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 3:35:46

数据链路层核心原理与实战排查:从帧结构到CRC、VLAN与MTU故障

做网络这些年&#xff0c;我越来越发现一个现象&#xff1a;很多人能把TCP握手、IP路由说得头头是道&#xff0c;但一遇到线上故障&#xff0c;翻来覆去就是查IP、看路由、重启服务&#xff0c;最后发现根源竟然在数据链路层。数据链路层是整个协议栈里最不起眼却又最“接地气”…

作者头像 李华
网站建设 2026/10/19 3:35:43

数据链路层从入门到排障:帧结构、MAC与VLAN实战解析

千兆网卡、六类网线、路由器全千兆口都换了&#xff0c;内网拷文件速度还是只有十几兆每秒&#xff0c;问题到底出在哪&#xff1f;如果你也遇到过这种诡异现象&#xff0c;大概率是卡在了 ISO 七层模型的第二层——数据链路层。很多人花大把时间调路由器、换网线&#xff0c;却…

作者头像 李华
网站建设 2026/10/19 3:34:49

Open-Code-Review落地实践:从代码评审到可检索资产

1. 为什么"open-code-review"值得单独拿出来聊第一次听到"open-code-review"这个词&#xff0c;很多人会下意识把它理解成"开源代码审查"或者"公开的代码评审"。这个理解不算错&#xff0c;但只停留在字面。我做了十多年研发和团队协作…

作者头像 李华
网站建设 2026/10/19 3:33:02

SpringBoot+Vue足球青训管理后台:从业务建模到权限控制实战

1. 项目全貌&#xff1a;一个足球青训管理后台到底在管什么作为长期带毕设的过来人&#xff0c;我见过太多选题一眼就能看出是“硬凑的”——图书馆管理系统、超市收银系统、会议室预约平台&#xff0c;界面套个模板就是一套。但“SpringBootVue足球青训俱乐部管理后台”这个题…

作者头像 李华
网站建设 2026/10/19 3:32:57

大模型引导深度强化学习:非地面网络资源分配实战与调优指南

大模型和深度强化学习放在一起跑非地面网络资源分配&#xff0c;这个方向我盯了有大半年。先说结论&#xff1a;这条路能走通&#xff0c;而且效果比单纯用DRL或者单纯用启发式算法都要好&#xff0c;但前提是你必须把“引导”这件事设计得很谨慎&#xff0c;否则大模型会教坏你…

作者头像 李华
网站建设 2026/10/19 3:31:48

open-code-review实战:从评审清单到协作文化的完整落地指南

1. 为什么“open-code-review”值得单独拿出来聊第一次听到“open-code-review”这个词&#xff0c;很多人会下意识把它理解成“把代码评审公开化”&#xff0c;或者“开源项目的代码评审流程”。这两种理解都不算错&#xff0c;但都只碰到了表面。我做了十多年研发&#xff0c…

作者头像 李华