返回

文章详情

C++26 反射的美丽类型擦除

Hacker News2026年7月14日 12:40

如果您曾尝试使用类型擦除来处理比 std::any 或 std::function 更复杂的情况,您可能会编写100多行容易出错的代码,或者选择像 Boost.TypeErasure 或 Folly.Poly 这样的模板繁重的库。rjk::duck 使用 C++26 反射的魔力来消除这些痛点,同时保留所有自定义和性能。考虑以下基本示例: ```cpp #include <rjk/duck.hpp> // ... struct [[ = rjk::trait ]] Container { auto size() const -> std::size_t; auto empty() const -> bool; auto clear() -> void; }; rjk::duck<Container> c{ std::vector<int>{1, 2, 3}}; c.size(); // 3 c = std::string{ "hello" }; // 运行时交换底层类型 c.size(); // 5 c = std::map<int, int>{{1, 2}, {3, 4}}; c.empty(); // false c.clear(); c.empty(); // true ``` 只需声明一次接口,让您已经编写的定义处理剩余部分。该库本身是一个单头文件包含。它提供拥有和非拥有语义、操作符、接口组合、现有接口的适配器、第三方类型的扩展方法等等。我们在这里谈论的是最前沿的技术,因此现在仅支持使用 `gcc` 和 `-std=c++26 -freflection`。duck 以一些独特的方式使用反射,超出了您可能看到的简单枚举到字符串或 JSON 序列化示例,因此我们将在接下来的文章中揭示使该库成为可能的技巧。特别是,我们将讨论标签生成、虚表代码生成、重载解析以及保持 duck 小巧的互换性技巧。 ### C++26 反射简介 您可能已经注意到上述示例中的奇怪语法 `[[=rjk::trait]]`。这是一个 C++26 注解,可以像属性一样应用于结构。trait 的实际定义如下: ```cpp constexpr inline struct {} trait {}; ``` 我们可以这样检查一个类型是否具有 trait 注解: ```cpp return std::ranges::any_of(annotations_of(^^ MyType), [](std::meta::info annotation) { return type_of(annotation) == type_of(^^ trait); }); ``` `^^` 操作符生成某个对象的反射。在这种情况下,我们正在反射一个类型(MyType)和一个变量(trait)。所有反射都具有 `std::meta::info` 类型,可以通过多种元函数进行查询,例如 `annotations_of` 和 `type_of`。 duck 过程中的第一步是解释 trait 的成员并将其转换为 tag,tag 是 duck 内部使用的格式。因此,对于这个 trait : ```cpp struct [[ = rjk::trait ]] MyTrait { auto foo() -> void; auto bar() const -> int; }; ``` 我们的目标是生成 `has_fn<"foo", auto() -> void>` 和 `has_fn<"bar", auto() const -> int>` 标签。我们可以通过检查 MyTrait 的成员并进行转换来轻松实现这一点: ```cpp consteval auto members_to_tags(std::meta::info trait) -> std::vector<std::meta::info> { const auto ctx = std::meta::access_context::unprivileged(); // 仅检查公共成员 return members_of(trait, ctx) | std::views::filter(std::meta::is_user_declared) // 排除构造函数等 | std::views::filter(std::meta::is_function) // 排除数据成员 | std::views::filter(std::meta::has_identifier) // 排除操作函数等 | std::views::transform([](std::meta::info member) { const fixed_string name { identifier_of(member) }; const auto signature = type_of(member); // 返回函数类型 const auto tag = substitute( ^^ has_fn, { reflect_constant(name), signature }); return tag; }) | std::ranges::to<std::vector>(); } ``` 实际实现需要处理多种其他方面,比如迭代 traits 的基类、处理 const trait、操作符等等。但核心转换看起来是如此简单。 ### 生成虚表 C++26 中获得的代码生成机制有限,但仍然非常强大。让我们逐步了解每个组件如何生成虚表,从创建结构开始: ```cpp template<is_trait... Traits> struct vtable_generator { struct vtable; // ... consteval { std::vector<std::meta::info> members { /* typeid, copy, move, destroy... */ }; constexpr static std::array<std::meta::info, sizeof...(Traits)> traits { ^^ Traits ...}; template for (constexpr auto trait : traits) { for (const auto tag : members_to_tags(trait)) { const auto args = template_arguments_of(tag); ```

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡