第31章 C++20与Pybind11
C++20 引入了许多强大的新特性。本章探讨如何将这些特性与 Pybind11 结合,以及哪些特性目前仍处于实验阶段。
31.1 Concepts支持
Section titled “31.1 Concepts支持”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 让绑定代码更易维护。
31.2 Ranges集成
Section titled “31.2 Ranges集成”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 可以在不创建中间容器的情况下链式转换数据。
31.3 Coroutines支持
Section titled “31.3 Coroutines支持”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 的 Coroutinestruct 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 的桥接目前还不成熟。主要限制包括:
- pybind11 对 C++ Coroutines 的支持是实验性的
- 编译器对 C++20 Coroutines 的支持程度不一
- C++ Coroutine 的内存管理模型与 Python 协程不同
当前推荐使用 Future/Promise 模式作为跨语言异步通信的桥梁。
31.4 模块化C++20特性
Section titled “31.4 模块化C++20特性”C++20 模块(Modules)是重要的语言特性,目前 Pybind11 正在逐步增加对模块的支持。
// 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 与 requirestemplate<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 | 完整 | 稳定 | 结构化初始化 |
最佳实践:
- Concepts 是最实用的 C++20 特性,可以显式表达模板约束
- Ranges 适合处理大数据集的惰性转换
- 原生 Coroutines 与 Python asyncio 的桥接仍需等待 pybind11 成熟
- 使用
std::future/std::promise作为可靠的跨语言异步模式 - 持续关注 pybind11 版本更新,跟进新特性支持