Skip to content

Ch 28: C++20 其他现代特性

  • 理解 consteval 强制编译期计算
  • 掌握 std::format 格式化
  • 熟练使用 std::span
  • 掌握三路比较运算符 <=>
  • 了解 C++20 其他实用特性
# Python - 所有计算在运行时
def factorial(n):
result = 1
for i in range(2, n + 1):
result *= i
return result
x = factorial(10) # 运行时计算
print(x) # 3628800
#include <iostream>
// constexpr - 可以是编译期或运行期
constexpr int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
int main() {
// fib(10) 可能在编译期计算
int x = fib(10); // 编译器决定:编译期或运行期
// 运行时输入
// int n; std::cin >> n;
// fib(n) // 保证运行期计算
std::cout << x << std::endl;
}
#include <iostream>
// consteval - 强制编译期求值
consteval int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
// 编译期验证
static_assert(factorial(5) == 120); // 编译时验证!
int main() {
// 编译期计算,结果直接嵌入
int x = factorial(10); // 编译期计算,3628800 直接作为常量
std::cout << x << std::endl;
// factorial(10.5); // 编译错误!必须是整数常量表达式
// 运行时值不能传给 consteval
// int n; std::cin >> n;
// factorial(n); // 编译错误!n 不是常量表达式
}
#include <iostream>
#include <array>
// constexpr:编译期或运行期
constexpr int comp_sum(int a, int b) {
return a + b;
}
// consteval:必须是编译期
consteval int const_sum(int a, int b) {
return a + b;
}
int main() {
// 两者都可以用于常量上下文
static_assert(comp_sum(1, 2) == 3);
static_assert(const_sum(1, 2) == 3);
// 差异:运行时参数
int a = 1, b = 2;
int r1 = comp_sum(a, b); // OK,编译期或运行期
// int r2 = const_sum(a, b); // 错误!参数必须是常量
constexpr int ca = 1, cb = 2;
int r3 = const_sum(ca, cb); // OK,constexpr 参数是常量
}
// consteval 用于元编程和性能关键代码
consteval int square(int x) { return x * x; }
consteval int cube(int x) { return x * x * x; }
// 编译期计算数组大小
consteval std::size_t array_size() { return 10; }
std::array<int, array_size()> arr; // 编译期确定大小
#include <iostream>
#include <array>
#include <type_traits>
// consteval 用于类型计算
consteval int fahrenheit_to_celsius(int f) {
return (f - 32) * 5 / 9;
}
consteval bool is_little_endian() {
int x = 1;
return *reinterpret_cast<char*>(&x) == 1;
}
// 编译期验证 endianness
static_assert(is_little_endian(), "Big endian not supported");
// consteval 用于 assert(在编译期检查)
consteval bool in_range(int v, int min, int max) {
return v >= min && v <= max;
}
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
// 编译期计算阶乘
static_assert(Factorial<5>::value == 120);
static_assert(Factorial<10>::value == 3628800);
int main() {
std::cout << "Factorial 10: " << Factorial<10>::value << "\n";
// 编译期温度转换
constexpr int freezing_f = 32;
constexpr int freezing_c = fahrenheit_to_celsius(freezing_f);
static_assert(freezing_c == 0);
std::cout << "Freezing point: " << freezing_c << "C\n";
}
#include <format>
#include <iostream>
#include <string>
int main() {
// 基本格式化
std::string s = std::format("Hello, {}!", "World");
std::cout << s << std::endl; // Hello, World!
// 多个占位符
std::string s2 = std::format("{} + {} = {}", 1, 2, 3);
std::cout << s2 << std::endl; // 1 + 2 = 3
// 指定位置
std::string s3 = std::format("{0} {1} {0}", "Hello", "World");
std::cout << s3 << std::endl; // Hello World Hello
// 命名参数(C++20 不支持,需要 C++23 或手写)
// std::string s4 = std::format("name={name}", std::format_arg("name", "Alice"));
}
#include <format>
#include <iostream>
#include <iomanip>
int main() {
// 浮点数精度
std::cout << std::format("PI = {:.4f}\n", 3.141592653); // PI = 3.1416
std::cout << std::format("PI = {:.2f}\n", 3.14159); // PI = 3.14
// 十六进制
std::cout << std::format("Hex: {:#x}\n", 255); // Hex: 0xff
std::cout << std::format("Hex: {:x}\n", 255); // Hex: ff
std::cout << std::format("Hex: {:02x}\n", 255); // Hex: ff
// 二进制
std::cout << std::format("Binary: {:b}\n", 42); // Binary: 101010
// 八进制
std::cout << std::format("Octal: {:o}\n", 64); // Octal: 100
// 科学计数法
std::cout << std::format("Scientific: {:.2e}\n", 123456.789); // 1.23e+05
}
#include <format>
#include <iostream>
int main() {
// 右对齐(默认)
std::cout << std::format("{:>10}\n", "right"); // right
// 左对齐
std::cout << std::format("{:<10}\n", "left"); // left
// 居中
std::cout << std::format("{:^10}\n", "center"); // center
// 填充字符
std::cout << std::format("{:*>10}\n", "pad"); // *******pad
std::cout << std::format("{:*<10}\n", "pad"); // pad*******
std::cout << std::format("{:*^10}\n", "pad"); // ***pad****
// 数字对齐
std::cout << std::format("{:+10}\n", 42); // +42
std::cout << std::format("{:<+10}\n", 42); // +42
}
#include <format>
#include <iostream>
int main() {
// 显示正负号
std::cout << std::format("{:+d}\n", 42); // +42
std::cout << std::format("{:+d}\n", -42); // -42
// 前导零
std::cout << std::format("{:05d}\n", 42); // 00042
std::cout << std::format("{:010d}\n", 42); // 0000000042
// 分组分隔符(C++23)
// std::cout << std::format("{:,}\n", 1234567); // 1,234,567
// 百分比
std::cout << std::format("{:.1%}\n", 0.75); // 75.0%
// 科学计数法
std::cout << std::format("{:.2e}\n", 123456.789); // 1.23e+05
}
#include <format>
#include <iostream>
#include <vector>
#include <string>
struct Person {
std::string name;
int age;
double height;
};
int main() {
// 结构体格式化
Person p{"Alice", 30, 165.5};
std::cout << std::format("Name: {:10s} Age: {:3d} Height: {:.1f}\n",
p.name, p.age, p.height);
// Name: Alice Age: 30 Height: 165.5
// 表格输出
std::vector<Person> people = {
{"Alice", 30, 165.5},
{"Bob", 25, 180.0},
{"Charlie", 35, 170.2}
};
std::cout << std::format("{:<10} {:>4} {:>6}\n", "Name", "Age", "Height");
std::cout << std::string(22, '-') << "\n";
for (const auto& person : people) {
std::cout << std::format("{:<10} {:>4} {:>6.1f}\n",
person.name, person.age, person.height);
}
}
// 传统 #include 的问题:
// 1. 编译速度慢 - 每次都要解析整个头文件
// 2. 宏污染 - 头文件中的宏会影响整个翻译单元
// 3. 重复编译 - 同一个头文件被多次解析
// foo.h
#ifndef FOO_H
#define FOO_H
#define MAX_SIZE 100 // 可能与其他头文件冲突
class Foo { /* ... */ };
#endif
// main.cpp
#include "foo.h" // 展开整个 foo.h
#include "bar.h" // 可能也定义了 MAX_SIZE
// ===== 模块定义文件 mathModule.cpp =====
// 注意:模块文件名和模块名不必相同
module; // 可选,表示这是模块接口
export module math; // 导出模块声明
// 私有模块片段(不影响外部可见性)
module privately;
// 导出接口
export int add(int a, int b) {
return a + b;
}
export constexpr int MAX_VALUE = 100;
// 非导出(模块内部)
namespace internal {
int helper(int x) { return x * 2; }
}
// ===== 使用模块的代码 =====
// main.cpp
import math; // 导入模块,而不是 #include
int main() {
int result = add(1, 2); // 直接使用,无歧义
// add 在 math 模块中定义,不存在宏污染
}
utils.h
// ===== 头文件方式 =====
#ifndef UTILS_H
#define UTILS_H
#define MAX 100 // 全局宏污染
inline int square(int x) { return x * x; } // 每次#include都重新实例化
struct Data { int value; };
#endif
// user1.cpp
#include "utils.h" // 展开 MAX,引入 square
// user2.cpp
#include "utils.h" // 再次展开,重复编译
// ===== 模块方式 =====
// mathModule.cpp
module;
export module math;
export inline int square(int x) { return x * x; }
export constexpr int MAX = 100;
// user1.cpp
import math; // 只导入一次
// user2.cpp
import math; // 共享模块接口,零重复编译
// 主模块
export module foo;
// foo 命名空间(模块内部)
export namespace foo {
void public_func();
}
namespace foo {
void private_func(); // 模块内部
}
// 分区模块
export module foo:detail; // foo 的 detail 分区
export void hidden_helper(); // 仅模块内部使用
// 使用
import foo;
import foo:detail; // 需要时显式导入分区
// ===== 编译器支持现状(2024) =====
// GCC 11+ - 基本支持
// Clang 15+ - 基本支持
// MSVC 19.29+ - 基本支持
// 注意:std 模块尚未标准化
// import std; // 大多数编译器不支持
// 当前最佳实践:
// - 模块主要用于大型项目加速编译
// - 标准库仍使用 #include
// - 混用头文件和模块是安全的
// 示例:混用
export module myapp; // 项目自定义模块
import std; // 标准库模块(未来)
#include <spdlog/spdlog.h> // 第三方库(当前只能头文件)
// ===== 推荐项目结构 =====
/project
├── src/
│ ├── main.cpp
│ ├── myapp.cpp // 模块实现
│ └── myapp.cppm // 模块接口(.cppm 是常用扩展名)
└── include/
└── myapp.h // 仍需要传统头文件(对外 API)
// ===== myapp.cppm =====
module;
export module myapp;
export import std.core; // 未来(C++26)
// import std; // C++26 目标
export {
void init();
int process(int);
}
// ===== 迁移策略 =====
// 1. 新代码用模块
// 2. 渐进式迁移现有头文件
// 3. 库优先提供模块接口(.cppm)
// 4. 头文件保持向后兼容
// 推荐工具:
// - cmake 的 CMakeLists.txt 支持 modules
// - build2 工具对 modules 支持较好
#include <span>
#include <vector>
#include <array>
#include <iostream>
// span - 连续内存的轻量视图
void print_span(std::span<int> data) {
for (int x : data) {
std::cout << x << " ";
}
std::cout << "\n";
std::cout << "size: " << data.size() << "\n";
}
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::array<int, 5> arr = {1, 2, 3, 4, 5};
int c_arr[] = {1, 2, 3, 4, 5};
// 从不同容器创建 span
print_span(vec); // 1 2 3 4 5
print_span(arr); // 1 2 3 4 5
print_span(c_arr); // 1 2 3 4 5
// 空 span
std::span<int> empty;
std::cout << "empty.size(): " << empty.size() << "\n"; // 0
}
#include <span>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::span<int> sp(vec);
// first(n) - 前 n 个元素
auto first3 = sp.first(3);
std::cout << "first(3): ";
for (int x : first3) std::cout << x << " "; // 1 2 3
std::cout << "\n";
// last(n) - 后 n 个元素
auto last3 = sp.last(3);
std::cout << "last(3): ";
for (int x : last3) std::cout << x << " "; // 8 9 10
std::cout << "\n";
// subspan(offset, count) - 子范围
auto sub = sp.subspan(2, 4); // 从索引2开始,4个元素
std::cout << "subspan(2, 4): ";
for (int x : sub) std::cout << x << " "; // 3 4 5 6
std::cout << "\n";
// 使用数组指针和大小创建
int* data = vec.data();
std::span<int> from_ptr(data + 1, 3); // 2, 3, 4
std::cout << "from_ptr: ";
for (int x : from_ptr) std::cout << x << " ";
std::cout << "\n";
}
#include <span>
#include <vector>
#include <array>
#include <iostream>
// 优势:接受多种容器类型
int sum_elements(std::span<const int> data) {
int sum = 0;
for (int x : data) {
sum += x;
}
return sum;
}
// 处理子范围
void process_window(std::span<int> window) {
for (auto& x : window) {
x *= 2;
}
}
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::array<int, 5> arr = {1, 2, 3, 4, 5};
int raw[] = {1, 2, 3, 4, 5};
// 统一接口
std::cout << sum_elements(vec) << "\n"; // 15
std::cout << sum_elements(arr) << "\n"; // 15
std::cout << sum_elements(raw) << "\n"; // 15
// 处理原始数组的子范围
std::span<int> sp(vec);
process_window(sp.subspan(1, 3)); // 处理 2, 3, 4
std::cout << "After: ";
for (int x : vec) std::cout << x << " "; // 1 4 6 8 5
std::cout << "\n";
}
#include <span>
#include <vector>
#include <iostream>
// 旧式 API
void old_api(const int* data, std::size_t size) {
for (std::size_t i = 0; i < size; ++i) {
std::cout << data[i] << " ";
}
std::cout << "\n";
}
// 现代 API
void modern_api(std::span<const int> data) {
for (int x : data) {
std::cout << x << " ";
}
std::cout << "\n";
// span 知道大小
if (!data.empty()) {
std::cout << "First: " << data.front() << "\n";
std::cout << "Last: " << data.back() << "\n";
}
// 边界安全(C++20)
// data[100]; // 运行时断言失败
}
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 旧式调用
old_api(vec.data(), vec.size());
// 现代调用
modern_api(vec);
// span 自动构造
modern_api(vec.subspan(1, 3)); // 2, 3, 4
}
#include <compare>
#include <iostream>
int main() {
// 三路比较返回 std::partial_ordering, std::weak_ordering, 或 std::strong_ordering
// strong_ordering: 相似于 ==,但值可以不同(如大小写不同的字符串)
// weak_ordering: 相似于 ==,忽略大小写等
// partial_ordering: 支持无序(如 NaN)
// spaceship 运算符 <=>
std::cout << (1 <=> 1) << "\n"; // 0 (equal)
std::cout << (1 <=> 2) << "\n"; // <0 (less)
std::cout << (2 <=> 1) << "\n"; // >0 (greater)
// 使用预定义值
auto r1 = 1 <=> 2;
std::cout << (r1 < 0) << "\n"; // 1 (true)
// 浮点数比较(partial_ordering)
double a = 1.0, b = 2.0;
auto r2 = a <=> b;
if (r2 < 0) std::cout << "a < b\n";
}
#include <compare>
#include <iostream>
class Version {
public:
Version(int major, int minor, int patch)
: major_(major), minor_(minor), patch_(patch) {}
// C++20:= default 自动生成所有比较运算符
auto operator<=>(const Version&) const = default;
private:
int major_, minor_, patch_;
};
int main() {
Version v1{1, 2, 3};
Version v2{1, 2, 3};
Version v3{1, 3, 0};
Version v4{2, 0, 0};
// 所有比较自动生成
std::cout << (v1 == v2) << "\n"; // 1 (true)
std::cout << (v1 != v3) << "\n"; // 1 (true)
std::cout << (v1 < v3) << "\n"; // 1 (true, minor < minor)
std::cout << (v1 <= v3) << "\n"; // 1 (true)
std::cout << (v4 > v1) << "\n"; // 1 (true, major > major)
}
#include <compare>
#include <iostream>
#include <string>
#include <cctype>
class CaseInsensitiveString {
public:
explicit CaseInsensitiveString(std::string s) : s_(std::move(s)) {}
// 自定义三路比较
auto operator<=>(const CaseInsensitiveString& other) const {
return case_insensitive_compare(s_, other.s_);
}
private:
static int case_insensitive_compare(const std::string& a, const std::string& b) {
auto to_lower = [](unsigned char c) { return std::tolower(c); };
std::string la(a.size(), ' '), lb(b.size(), ' ');
std::transform(a.begin(), a.end(), la.begin(), to_lower);
std::transform(b.begin(), b.end(), lb.begin(), to_lower);
if (la < lb) return -1;
if (la > lb) return 1;
return 0;
}
std::string s_;
};
int main() {
CaseInsensitiveString s1("Hello");
CaseInsensitiveString s2("hello");
CaseInsensitiveString s3("World");
std::cout << (s1 == s2) << "\n"; // 1 (true, case insensitive equal)
std::cout << (s1 < s3) << "\n"; // 1 (true, Hello < World)
}
# Python - 所有比较直观
class Version:
def __init__(self, major, minor, patch):
self.major = major
self.minor = minor
self.patch = patch
def __eq__(self, other):
return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)
def __lt__(self, other):
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
# Python 3 支持元组比较
v1 = (1, 2, 3)
v2 = (1, 3, 0)
print(v1 < v2) # True
#include <map>
#include <iostream>
int main() {
// C++17: if 初始化
if (auto it = std::map<int, std::string>{{1, "one"}, {2, "two"}}.find(1);
it != nullptr) {
std::cout << "Found: " << it->second << "\n";
}
// C++20: 范围 for 初始化
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto v = vec; auto& x : v) {
x *= 2; // vec 变成 {2, 4, 6, 8, 10}
}
for (int x : vec) {
std::cout << x << " ";
}
std::cout << "\n";
}
#include <iostream>
#include <optional>
enum class Mode { Read, Write, Append };
// 模拟返回值的函数
std::optional<Mode> get_mode() {
return Mode::Read;
}
int main() {
// switch 初始化(C++20)
switch (auto result = get_mode(); result.value_or(Mode::Read)) {
case Mode::Read:
std::cout << "Mode: Read\n";
break;
case Mode::Write:
std::cout << "Mode: Write\n";
break;
case Mode::Append:
std::cout << "Mode: Append\n";
break;
}
}
#include <iostream>
// constinit - 保证静态初始化
constinit int static_value = 42;
// 避免静态初始化顺序问题
const int& get_static() {
static int value = compute(); // 可能初始化顺序问题
return value;
}
// constinit 确保静态变量在模块初始化时初始化
static constinit int early_init = 10;
// 组合:constinit + constexpr
constinit constexpr int arr[] = {1, 2, 3};
int main() {
std::cout << "static_value: " << static_value << "\n";
std::cout << "early_init: " << early_init << "\n";
}
#include <iostream>
int main() {
// 分支提示:帮助编译器优化
for (int i = 0; i < 100; ++i) {
if (i > 50) [[likely]] {
std::cout << i << " ";
}
}
std::cout << "\n";
// likely/unlikely 可以提高分支预测效率
int x = 42;
if (x > 100) [[unlikely]] {
std::cout << "Rare case!\n";
} else {
std::cout << "Common case\n";
}
}
#include <algorithm>
#include <array>
#include <iostream>
int main() {
// constexpr 算法(C++20)
constexpr std::array<int, 5> data = {5, 2, 8, 1, 9};
// 编译期排序
constexpr auto sorted = [] {
std::array<int, 5> arr = data;
std::sort(arr.begin(), arr.end());
return arr;
}();
static_assert(sorted[0] == 1);
static_assert(sorted[4] == 9);
// constexpr std::vector (C++20)
// constexpr std::vector<int> vec{1, 2, 3}; // C++26 可能支持
std::cout << "Sorted: ";
for (int x : sorted) {
std::cout << x << " ";
}
std::cout << "\n";
}
// ===== 高实用性(立即可用) =====
#include <concepts>
#include <ranges>
#include <format>
#include <span>
// 1. Concepts - 更好的模板约束
template<std::integral T>
T add(T a, T b) { return a + b; }
// 2. Ranges - 惰性视图和管道
auto result = nums
| std::views::filter([](int x) { return x > 0; })
| std::views::transform([](int x) { return x * x; });
// 3. std::format - 类型安全格式化
auto s = std::format("{} + {} = {}", 1, 2, 3);
// 4. std::span - 非拥有视图
void process(std::span<int> data);
// 5. constexpr 算法
constexpr auto sum = std::accumulate(std::views::iota(1, 11), 0);
// 6. 初始化语句
if (auto it = m.find(key); it != m.end()) { /* ... */ }
// 7. <=> 运算符
class Vec {
auto operator<=>(const Vec&) const = default;
};
// 8. consteval - 强制编译期
consteval int square(int x) { return x * x; }
// ===== 需要编译器支持(C++20 已完成但部分实现) =====
// 9. std::expected (C++23)
// 10. std::flat_map (C++23)
// 11. std::flat_set (C++23)
// 12. ranges::to (C++23)
// 协程 - 复杂,库支持有限
// #include <coroutine> // 实际项目用 libasio 或其他库
// Modules - 工具链支持不完整
// import std; // 大多数编译器不支持
// 等待广泛支持后再使用这些特性

consteval vs constexpr:

特性constevalconstexpr
求值时机必须编译期编译期或运行期
运行时参数不支持支持
用于常量上下文支持支持

std::format 占位符:

占位符说明示例
{}默认格式{}
{:d}整数{:d}
{:f}浮点{:.2f}
{:x}十六进制{:#x}
{:>10}右对齐{:>10}
{:<10}左对齐{:<10}
{:^10}居中{:^10}

span 视图操作:

操作说明
first(n)前 n 个元素
last(n)后 n 个元素
subspan(o,c)从 o 开始取 c 个
size()元素数量
empty()是否为空
data()原始指针

三路比较返回值:

  • std::strong_ordering:等价性可传递(如 ==)
  • std::weak_ordering:等价性不可传递(如忽略大小写比较)
  • std::partial_ordering:支持无序(如浮点数 NaN)

下章预告:ch29 学习错误处理,理解异常安全和 noexcept。