A quick recap Annotations 能够给代码结构附上一些额外信息,供 Reflection 随后查找。 直接上个例子: struct label { int value; }; inline constexpr label answer{42}; [[=answer]] int x; consteval label annotation_value() { auto annotations = std::meta::annotations_of(^^x); return std::meta::extract<label>(annotations[0]); } int main() { constexpr auto count = std::meta::annotations_of(^^x).size();… Continue Reading

引言 盛夏到了,赤日当空,蝉吟鹊噪,绿树浓阴如盖,万物争展其生机。 静态反射方冒出头,便给 C++ 注入了久违的活力。这是一个全新的语言。新在哪里呢?当然不是反射本身,而是伴随反射而来的代码生成能力。从宏元编程,到模板元编程,再到如今的反射元编程,岁月苒苒,大家会认识到这是 C++ 新纪元的开始。 C++26 静态反射通过 Spec API 注入模型来提供反射元编程能力,尽管这只是一种略显笨重的注入机制,不如 Fragments、Token Sequences 等注入模型灵活,但至少当前就能使用,能够初步实现反射元编程了。 静态反射早在 CGP 一书的最后两章写过,变化实轻,便不絮繁。本文只回顾并补充注入模型的相关内容。 Spec API 注入模型 C++26 的 Spec API 注入模型包含两个元函数:define_aggregate() 和 data_member_spec()。声明如下: namespace std::meta { struct data_member_options { struct name_type { template <typename T>… Continue Reading

去年,我在“What does the …… mean in C++?”一文中专门介绍过 ...... 这种极其罕见的 C++ 语法,它其实是 ..., ... 的省略语法。以下这三种写法完全等价: template<class... Args> void f(Args......) {} template<class... Args> void f(Args... ...) {} template<class... Args> void f(Args..., ...) {} 第一个 ... 用于扩展 C++ 可变模板参数包,而第二个 ... 用于匹配 C 语言中… Continue Reading

std::projected 是 C++20 引入的一个模板工具,用于获取应用投影函数后迭代器所指向的值类型。例如: struct Person { std::string name; int age; }; int main() { std::vector<Person> people = {{"Alice", 30}, {"Bob", 25}, {"Charlie", 35}}; auto proj = [](const Person& p) { return p.age; }; using ProjAge = std::projected<std::vector<Person>::iterator, decltype(proj)>; static_assert(std::is_same_v<ProjAge::value_type,… Continue Reading

Motivation of std::function_ref std::function_ref 是轻量级的 std::function,能够引用任意形式的可调用对象。在此之前,C++ 便已存在许多同类问题下的解决策略,如函数指针、仿函数、模板、std::function、std::move_only_function……为何又要引入 std::function_ref 呢? 对于函数指针,虽然在需要保存函数的场景下能够最大化性能,但是它无法持有状态,也无法使用 Lambda 和 std::bind 等常见方式传参。采用这种策略,便舍弃了灵活性、通用性和安全性,通常是得不偿失。 对于仿函数,它能够持有状态,可用性更好,只是简洁性不足。Lambda 弥补了这个缺点,可以直接原地构造一个携带状态的函数,而无须再去创建一个额外的仿函数。Lambda 与模板配合使用,不会存在额外的开销,性能与函数指针基本持平。只是,相比起来,模板这种策略实现起来稍显复杂,需要对模板参数施加函数签名约束,并且无法分离声明与实现,微小变化也会导致重新编译。同时,由于 Lambda 函数的类型无法被存储,如果想要存储具体的可调用对象的类型,这种策略便无能为力,它在通用性和灵活性方面仍有不足。 对于 std::function 和 std::move_only_function,它们易于使用,能够存储任意形式的可调用对象,通用性和灵活性更强,但可用性又落了下风。主要表现在两个方面:一是它们分别要求目标对象具有拷贝或移动构造函数,二是开销较大。 std::function_ref 解决了以上策略的不足,其在简洁性、可用性和通用性等方面都具有较好的表现。下表对比了各种策略在不同设计目标下的表现: 策略 简洁性 美感 可用性 通用性 函数指针 中 中 中 低 模板 中 中 高 中… Continue Reading

元宵三五,夜雪初霁,蕙风透寒。 乘暇再更一个 C++26 的小特性,动机简单,看一下小例子: using Point3D = std::tuple<float, float, float>; int main() { auto [x, y, z] = Point3D(1, 2, 3); return x; } y 和 z 皆未使用,可仍旧要起一个名称,实显多余。新特性允许使用 _ 作为占位符,忽略不关心的值。是以可以这样写: using Point3D = std::tuple<float, float, float>; int main() { auto… Continue Reading

目前进入 C++26 的特性当中,Pack Indexing 是较为有用的一个,值得谈谈。 发展背景 早期,C++ 元编程是摸着石头过河,许多特性只是当时情况下的权宜之计,并非最理想的解决方式。纵然非常巧妙,却也治标不治本,诸多简单功能,写来亦是繁琐不已。 扬汤止沸,莫若去薪。不断向下一阶段发展的元编程,就是要彻底解决早期妥协所留下的问题,提供最优雅的解决方式,摆脱奇技淫巧带来的复杂性。 Pack Indexing 就是在这种背景下所诞生的一个新特性,提了多年,终于进入 C++26。 在此之前,C++ 就有一些与参数包相关的增强特性,比如 C++17 Fold expressions 和 Using-declarations,C++20 Lambda capture,还有原本打算进入 C++23 却一直悬而未决的 Expansion statements(最近被人重拾,兴许会入 C++26)。 此间,也有一些处于发展中的其他特性,比如 Pack declarations、Pack slicing 和 Pack literals,Pack Indexing 就是其中之一,它最先进了标准。 新的索引式访问方式 当前,若要定义一个参数包变量,我们需要借助 std::tuple;若要索引式访问参数包元素,需要借助 std::get… Continue Reading

岁月如驰,距前篇反射文章,已近两载。 C++23 工作殆尽,C++26 紧随其后,Relection 也有了一些新进展,本篇来看这些更新的些许内容。 相关作者 知识似树,发枝散叶,往往只需两三人而已。欲了解一个领域,先知悉其中的几位关键人物,由此扩散挖掘,便可以快速理解该领域 80% 以上的内容。 是以本节介绍一下 SG7 中 Reflection 相关的研究人员。 先从 Wyatt Childers 说起,他是 Lock3 Software 的软件工程师,主要就是研究实现静态反射和元编程。lock3 版本的反射就是他们写的,在 C++ 反射 第四章 标准 中便是使用的这一版本进行示例编写。 而 Lock3 Software 公司的创始人是 Andrew Sutton,此人就是 C++20 Concepts 提案的作者,也是 GCC Concepts 的主要开发人员。他于 2010-2013… Continue Reading