Skip to content

第31章 C++20与Pybind11

C++20 引入了许多强大的新特性。本章探讨如何将这些特性与 Pybind11 结合,以及哪些特性目前仍处于实验阶段。

C++20 Concepts 提供了更清晰的模板约束,可以改善编译错误信息。

#include <pybind11/pybind11.h>
#include <concepts>
#include <type_traits>
#include <vector>
#include <string>
#include <iostream>
namespace py = pybind11;
// C++20 Concepts: 定义类型约束
template<typename T>
concept NumericType = std::integral<T> || std::floating_point<T>;
template<typename T>
concept StringLike = requires(T a) {
{ a.empty() } -> std::convertible_to<bool>;
{ a.size() } -> std::convertible_to<size_t>;
};
// 约束函数模板
template<NumericType T>
T add(T a, T b) {
return a + b;
}
template<NumericType T>
T multiply(T a, T b) {
return a * b;
}
template<StringLike T>
size_t length(const T& s) {
return s.size();
}
// 带 concept 约束的类模板
template<typename T>
requires NumericType<T>
class NumericContainer {
public:
void add(T value) { data_.push_back(value); }
T sum() const {
T total = T();
for (const auto& v : data_) total += v;
return total;
}
size_t size() const { return data_.size(); }
const std::vector<T>& get_data() const { return data_; }
private:
std::vector<T> data_;
};
// Concepts 用于重载解析
template<typename T>
requires std::integral<T>
T process_value(T value) {
return value * 2;
}
template<typename T>
requires std::floating_point<T>
T process_value(T value) {
return value * 2.0;
}
// 使用 requires 子句的函数
template<typename T>
requires requires(T a, T b) { a + b; }
T safe_add(T a, T b) {
return a + b;
}
PYBIND11_MODULE(cpp20_concepts, m) {
m.doc() = "C++20 Concepts demo module";
// 导出受约束的函数模板
m.def("add", &add<int>, "Add two integers");
m.def("add", &add<double>, "Add two doubles");
m.def("multiply", &multiply<int>, "Multiply two integers");
m.def("multiply", &multiply<double>, "Multiply two doubles");
m.def("length", &length<std::string>, "Get string length");
// 导出受约束的类模板
py::class_<NumericContainer<int>>(m, "IntContainer")
.def(py::init<>())
.def("add", &NumericContainer<int>::add)
.def("sum", &NumericContainer<int>::sum)
.def("size", &NumericContainer<int>::size);
py::class_<NumericContainer<double>>(m, "DoubleContainer")
.def(py::init<>())
.def("add", &NumericContainer<double>::add)
.def("sum", &NumericContainer<double>::sum)
.def("size", &NumericContainer<double>::size);
// 导出 process_value 重载
m.def("process_value", &process_value<int>);
m.def("process_value", &process_value<double>);
// 导出 safe_add
m.def("safe_add", &safe_add<int>);
}
import cpp20_concepts as m
print(f"add(1, 2) = {m.add(1, 2)}")
print(f"add(1.5, 2.5) = {m.add(1.5, 2.5)}")
print(f"multiply(3, 4) = {m.multiply(3, 4)}")
text = "Hello, World!"
print(f"length('{text}') = {m.length(text)}")
int_container = m.IntContainer()
int_container.add(10)
int_container.add(20)
int_container.add(30)
print(f"IntContainer sum: {int_container.sum()}")
double_container = m.DoubleContainer()
double_container.add(1.1)
double_container.add(2.2)
print(f"DoubleContainer sum: {double_container.sum()}")
print(f"process_value(5) [int] = {m.process_value(5)}")
print(f"process_value(3.14) [double] = {m.process_value(3.14)}")

关键洞察:C++20 Concepts 使得模板约束显式化,编译器能给出更清晰的错误信息。虽然 Pybind11 仍需显式实例化,但 Concepts 让绑定代码更易维护。

C++20 Ranges 提供了统一的容器视图,与 Python 的生成器/迭代器概念相似。

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <ranges>
#include <vector>
#include <algorithm>
#include <iostream>
namespace py = pybind11;
namespace views = std::views;
namespace ranges = std::ranges;
// Range 视图:链式操作
template<std::ranges::range Range>
auto filter_range(Range&& range, std::function<bool(std::ranges::range_value_t<Range>)> pred) {
return range | views::filter(pred);
}
template<std::ranges::range Range>
auto transform_range(Range&& range, std::function<std::ranges::range_value_t<Range>(std::ranges::range_value_t<Range>)> func) {
return range | views::transform(func);
}
// 使用 Range 的函数
std::vector<int> range_operations(const std::vector<int>& data) {
auto result = data
| views::filter([](int x) { return x % 2 == 0; })
| views::transform([](int x) { return x * 2; })
| views::take(5);
return std::vector<int>(result.begin(), result.end());
}
// 生成范围
std::vector<int> generate_range(int start, int end, int step) {
auto view = views::iota(start, end)
| views::filter([](int x) { return x % step == 0; });
return std::vector<int>(view.begin(), view.end());
}
// Range 作为 Python 可迭代对象
py::list to_python_list(std::ranges::range auto&& range) {
py::list result;
for (auto&& item : range) {
result.append(py::cast(item));
}
return result;
}
PYBIND11_MODULE(cpp20_ranges, m) {
m.doc() = "C++20 Ranges demo module";
// 导出 Range 操作函数
m.def("range_operations", &range_operations,
"Filter even numbers, double them, take first 5");
m.def("generate_range", &generate_range,
"Generate range from start to end with step");
m.def("to_python_list", &to_python_list,
"Convert any range to Python list");
// 演示 Python 可迭代对象 → C++ Range → Python list
m.def("process_iterable", [](py::iterable input) {
std::vector<int> vec;
for (py::handle item : input) {
vec.push_back(py::cast<int>(item));
}
auto result = vec
| views::filter([](int x) { return x > 0; })
| views::transform([](int x) { return x * x; })
| views::reverse;
return to_python_list(result);
});
// 惰性求值示例:返回 Range 视图的工厂函数
m.def("make_filter_view", [](py::iterable input, int threshold) {
std::vector<int> vec;
for (py::handle item : input) {
vec.push_back(py::cast<int>(item));
}
// 返回 lambda,在 Python 端迭代时求值
return [vec, threshold](py::object callback) {
auto filtered = vec
| views::filter([threshold](int x) { return x > threshold; });
for (int x : filtered) {
callback(x);
}
};
});
}
import cpp20_ranges as m
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = m.range_operations(data)
print(f"range_operations({data}) = {result}")
generated = m.generate_range(0, 100, 5)
print(f"generate_range(0, 100, 5) = {generated}")
python_list = [1, 2, 3, 4, 5]
result = m.process_iterable(python_list)
print(f"process_iterable({python_list}) = {result}")
result = m.process_iterable([-3, -2, -1, 0, 1, 2, 3])
print(f"process_iterable([-3,-2,-1,0,1,2,3]) = {result}")
def print_item(x):
print(f" Item: {x}")
print("Items > 2:")
m.make_filter_view([1, 2, 3, 4, 5], 2)(print_item)

关键洞察:C++20 Ranges 的惰性求值特性非常适合处理大数据集。与 Python 的生成器类似,Ranges 可以在不创建中间容器的情况下链式转换数据。

C++20 Coroutines 尚处于实验阶段,pybind11 对其支持有限,但可以尝试。

#include <pybind11/pybind11.h>
#include <coroutine>
#include <future>
#include <thread>
#include <chrono>
#include <iostream>
namespace py = pybind11;
// 注意:C++20 Coroutines 支持仍处于实验阶段
// 以下代码可能在不同编译器上有不同的行为
// 简单的 Coroutine 返回类型
struct AwaitableInt {
std::future<int> future_;
bool await_ready() const {
return future_.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
}
int await_resume() {
return future_.get();
}
void await_suspend(std::coroutine_handle<> handle) {
std::thread([this, handle]() {
future_.get(); // 等待完成
handle.resume(); // 恢复协程
}).detach();
}
};
// 基于 Promise 的 Coroutine
struct IntPromise;
struct IntCoroutine {
using promise_type = IntPromise;
std::coroutine_handle<promise_type> handle_;
explicit IntCoroutine(std::coroutine_handle<promise_type> h) : handle_(h) {}
IntCoroutine(IntCoroutine&& other) noexcept : handle_(other.handle_) {
other.handle_ = nullptr;
}
~IntCoroutine() { if (handle_) handle_.destroy(); }
};
struct IntPromise {
int value_ = 0;
auto get_return_object() {
return IntCoroutine{std::coroutine_handle<IntPromise>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
int return_value(int v) {
value_ = v;
return v;
}
auto yield_value(int v) {
value_ = v;
return std::suspend_always{};
}
};
// C++ Coroutine 函数示例(编译器支持有限)
/*
IntCoroutine async_increment(int value) {
co_await std::suspend_always{};
co_return value + 1;
}
*/
// 使用 std::future 作为桥接(更可靠的方式)
py::object async_computation(int input) {
auto future = std::async(std::launch::async, [input]() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return input * 2;
});
// 返回一个可以被 Python await 的对象
// 由于 pybind11 对 coroutines 支持有限,使用 Future 包装
py::dict result;
result["value"] = future.get();
result["status"] = "completed";
return result;
}
// 实验性的协程桥接
py::object create_coro_handle(py::function coro_func) {
// 这是一个实验性的尝试
// 真正的 C++ coroutine → Python async 桥接需要更多工作
return py::none();
}
PYBIND11_MODULE(cpp20_coroutines, m) {
m.doc() = "C++20 Coroutines (experimental)";
// 目前最安全的方式是使用 Future/Promise 而不是原生 Coroutines
m.def("async_computation", &async_computation,
"Perform async computation and return result");
// 实验性功能
m.def("create_coro_handle", &create_coro_handle,
"Experimental coroutine handle bridge");
// 协程状态查询
m.def("get_coroutine_info", []() {
py::dict info;
info["cpp20_coroutines_supported"] = __cpp_lib_coroutine;
info["note"] = "Native coroutine support in pybind11 is limited";
info["recommendation"] = "Use Future/Promise pattern instead";
return info;
});
}
import cpp20_coroutines as m
info = m.get_coroutine_info()
print(f"C++20 Coroutines Support: {info}")
result = m.async_computation(42)
print(f"async_computation(42) = {result}")
try:
handle = m.create_coro_handle(lambda: None)
print(f"Coroutine handle: {handle}")
except Exception as e:
print(f"Coroutine bridge not available: {e}")

关键洞察:C++20 Coroutines 与 Python asyncio 的桥接目前还不成熟。主要限制包括:

  1. pybind11 对 C++ Coroutines 的支持是实验性的
  2. 编译器对 C++20 Coroutines 的支持程度不一
  3. C++ Coroutine 的内存管理模型与 Python 协程不同

当前推荐使用 Future/Promise 模式作为跨语言异步通信的桥梁。

C++20 模块(Modules)是重要的语言特性,目前 Pybind11 正在逐步增加对模块的支持。

hello.cpp
// C++20 Modules 示例(实验性)
// 注意:需要编译器支持 modulesTS
// export module hello; // C++20 模块语法
//
// export int add(int a, int b) { return a + b; }
// 传统模块化代码(仍然兼容 pybind11)
#include <pybind11/pybind11.h>
#include <string_view>
#include <format>
namespace py = pybind11;
// 使用 std::format(C++20)
std::string format_greeting(std::string_view name, int times) {
return std::format("Hello, {}! (x{})", name, times);
}
// 使用 std::span(C++20)
int sum_span(std::span<const int> numbers) {
int total = 0;
for (int n : numbers) total += n;
return total;
}
// constexpr std::数组算法
constexpr auto double_values(std::array<int, 5> arr) {
std::array<int, 5> result{};
for (size_t i = 0; i < arr.size(); ++i) {
result[i] = arr[i] * 2;
}
return result;
}
// designated initializers(C++20)
struct Point3D {
int x;
int y;
int z;
};
Point3D create_point3d(int x, int y, int z) {
return {.x = x, .y = y, .z = z}; // C++20 designated initializer
}
// concept 与 requires
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
};
template<Addable T>
T add_all(std::initializer_list<T> values) {
T result = T();
for (const auto& v : values) {
result = result + v;
}
return result;
}
PYBIND11_MODULE(cpp20_features, m) {
m.doc() = "Modern C++20 features in pybind11";
// std::format
m.def("format_greeting", &format_greeting,
"Use std::format for string formatting");
// std::span
m.def("sum_span", &sum_span,
"Use std::span for array-like arguments");
// constexpr 数组
m.def("double_values", &double_values,
"Constexpr array transformation");
// designated initializers
m.def("create_point3d", &create_point3d,
"Use C++20 designated initializers");
// concept
m.def("add_all", &add_all<int>,
"Use concept to constrain template");
m.def("add_all", &add_all<double>,
"Use concept to constrain template");
}
import cpp20_features as m
greeting = m.format_greeting("World", 3)
print(f"Greeting: {greeting}")
numbers = [1, 2, 3, 4, 5]
total = m.sum_span(numbers)
print(f"sum_span({numbers}) = {total}")
original = [1, 2, 3, 4, 5]
doubled = m.double_values(original)
print(f"double_values({original}) = {doubled}")
point = m.create_point3d(10, 20, 30)
print(f"Point3D: x={point['x']}, y={point['y']}, z={point['z']}")
result = m.add_all([1, 2, 3, 4, 5])
print(f"add_all([1,2,3,4,5]) = {result}")
result_float = m.add_all([1.1, 2.2, 3.3])
print(f"add_all([1.1,2.2,3.3]) = {result_float}")

C++20特性总结:

特性Pybind11 支持成熟度说明
Concepts完整稳定改善模板错误信息
Ranges完整稳定惰性迭代,链式操作
Coroutines有限实验性建议使用 Future/Promise
Modules发展中早期需要编译器完整支持
std::format完整稳定字符串格式化
std::span完整稳定数组视图
designated initializers完整稳定结构化初始化

最佳实践:

  1. Concepts 是最实用的 C++20 特性,可以显式表达模板约束
  2. Ranges 适合处理大数据集的惰性转换
  3. 原生 Coroutines 与 Python asyncio 的桥接仍需等待 pybind11 成熟
  4. 使用 std::future / std::promise 作为可靠的跨语言异步模式
  5. 持续关注 pybind11 版本更新,跟进新特性支持