凄凄微霜,靡靡年尽。 今岁是本号的第六年,朔其建立,不过蓦然起意,随性而记,阅者寥寥,未想习惯悄成,数载似梦,写作依旧。尔今随性依旧,暇日兴起,便构思编例,查经观典,细复完善,更得三两文章。 我写文颇自矜重,每有想法,必要深挖见底,思虑再三,始提笔撰文。是以文章结构完整,主题分明,单则是文,合则成书。读书更文,亦不以数量而论,而以知识点为单位。故桌上之书,时几本俱开,添得三两文献,对照观之,每有所得,意兴弥逸。但有选题,辄觅多点联系,力求尽美尽善。遂数年既过,不过百篇而已。 杨柳垂垂,清风袅袅。我与墨梵、邹启翔、水滴会飞、404 汇 The Book of Modern C++,将平日所读文章,按题选类,裒然成秩,俾优文广传,幽隐可显。 烈日炎炎,芭蕉冉冉。二三春社群方开,距二一冬已一年有余,人数激增,技术奇巧,靡不讨论。既而汇书发布,得一时暇光,续作分享,间以更文。行云悄逝,月落参横,寻常淡日,指间捻过。 秋阳杲杲,秋草摇摇。我与数友出门游玩,云白气清,山颦翠色,土湿青苔,暖风透爽。沿途游者众多,云䯿亭亭,翠袖笼香,粉吹旖旎,玉立娉婷。悠长大道,空旷绿树,骑驰其间,琐事尽置脑后。残阳隐隐,灯影昏昏,累登山巅,斜倚云屏,观烟琐归鸦,感物换星移。如此江山,人在蓬壶,图画中间。星影渐稀,残灯不明,便踏月而归,落叶响空林,谈笑声更盛,叹碧宇澄空,一日荡悠悠。 玉霙霏霏,霜风淒凄。我为尘事相羁,更文见少。时有呼声欲入社群,于是短短半年,二三冬开放。此次一开,主群殆满,下次知是何年?墨梵译书新出,多有见贻,我以近年多看电子书,且早已读过婉拒,遂在群内抽奖赠送,俾物尽其用矣。 云萍聚散,春秋奄冉而过。编程既久,少年之心不减,依旧弥日不倦,只是此身茫茫,不能屡为所好而作。 CppMore 网站今年建立,以往文章,陆续迁移,以编辑称意,修改方便,管理轻松。所有迁移与所撰新文,按照分类及时间,皆自动汇总于 https://www.cppmore.com/articles/。 年杪,文尾,我又呼朋引伴,寻地跨年。花灯照夜,兰舟载月,熙熙语笑,遥迎甲辰。踪迹沉浮又一载,多少韶光,几许消沉,今夕随风去。 今岁今宵尽,莺语哗声中,微闻歌者曰: 空空兮寂城,灿灿兮寒星。 春秋兮不止,日月兮弗停。 追求兮数载,半世兮浮萍。 倦倚兮西风,孤灯兮暗影。 有悲兮有兴,无雨兮无晴。 遥途兮漫长,浩荡兮驰行。 语尽,夜已深,炮声不绝,自语云,愿新年,胜旧年。

再再再补充一个重载决议的例子。 大家可能在某些地方见过 ::std:: 这样的代码,比如 ::std::swap,::std::vector,::std::nullptr_t。 在 Qualified Name Lookup 一节的子节 Namespace Member Lookup 已经介绍,名称前面以 :: 修饰表示在全局作用域下查找。 一个例子: namespace A { namespace B { void f() { std::cout << "A::B::f()\n"; } } } namespace B { void f() { std::cout << "B::f()\n"; } } namespace A { void h() { ::B::f();… Continue Reading std:: versus ::std::

霜风凄紧,神寒骨冷,忽忽冬月将尽。忙处更新渐少,今来补上。 本篇讲解如何利用 ChatGPT 快速实现一个控制台进度条小工具,相比单纯介绍某些特性,此种方式涉及知识的综合运用,也顺便谈谈如何结合 AI 进行编程。 问题描述 控制台程序执行一些耗时任务时,需要向用户显示当前任务执行的进度,以提供清晰的感知。比如一个下载程序,通过进度条便能告知用户当前的下载进度。 进度条可以单独显示,也可以在程序输出的最下方显示,下图是一个示例。 这是一种单控制条需求,执行任务,显示进度,输出流依旧是从上至下依序进行,适合单线程的场景。 多控制条显示的效果如下图,实现要更加复杂一些,本文暂时不会涉及该部分。 初步分析 控制台上显示的这种符号,称为 ASCII Art,就是以字符构建的某种图案,不借助图片,也能够有一个生动的展示效果,比如下图这种。 因此控制台进度条也称为 ASCII Progress Bar,通过字符图案来模拟进度条的显示,通常分为已完成部分及未完成部分,使用两个字符,动态改变字符数量,便能够模拟出一个进度条。 模拟方式既定,下一问题在于进度条刷新。如果每更新一次进度,便输出一个字符图案,那么屏幕上将满是进度条,需要针对一条进度条,不断刷新其数据,而非每次都输出一条新的。具体实现时,便需要寻找定位进度条的方法,每次清除当前数据,重新打印新的数据,视觉上显示的是连续动画。 刷新思路亦成,接着的问题在于如何在进度条之上插入其他输出。进度条始终显示在用户输出下方,因此每次用户输出时,可以立即定位到进度条,定位之后清除当前进度条,输出用户内容,再重新打印进度条,便能够达到这一效果。 细枝末节,便需依赖具体的实现手法。 借助 ChatGPT 快速构建基本代码 需求明确,思路既定,接着便要着手设计库的结构和细节,实现细节这部分代码无需从零编写,可以借助 AI 快速生成。 我们所需做的,就是详细描述需求,以及预想的思路,让 ChatGPT 生成代码,验证是否符合需求,若不符合,纠正错误,让它再次生成,不断重复这个过程,直到基本满足期待的效果。如果一开始的效果就完全牛头不对马嘴,那也可以让它基于 Python 生成,等到效果尚可,再让它把代码转换成 C++ 代码。 经过多次调教,最终生成的代码如下: #include <iostream> #include <thread> #include <chrono> void print_progress_bar(int iteration, int total, int bar_length =… Continue Reading 借助 ChatGPT 快速实现一个轻量级的控制台进度条库

模板加约束,其中有迷思,着意分析一下。 template <typename T> void f(T t) {} template <typename T> void f(T t) requires true {} f(1); // the second one wins 二比一特殊,决战胜出,似乎全在预料之内。 根据 [temp.over.link]/7: Two function templates are equivalent if they are declared in the same scope, have the same name, have equivalent template-heads, and have return types,… Continue Reading T231212 Dummy Rquires Clause Positioning in Function Template Overloading

回顾上篇实现,可见会遇到内存重新分配问题,大文件读取存在隐性开销。 using result_type = std::vector<std::vector<std::string>>; auto read_csv(std::string_view file, std::string_view type = "", std::string_view delimiter = ",") -> std::optional<result_type> { std::ifstream data_file(file.data()); if (!data_file.is_open()) { return {}; } std::string line; std::getline(data_file, line); // skip the title result_type result; while (std::getline(data_file, line)) { auto tokens = line | std::views::split(delimiter) | std::views::transform([](auto&& token)… Continue Reading Memory Reallocation when Parsing CSV Files

开窗见月,霜天悄然,欲更小文,以为消遣。 本篇以解析 CSV 为例,再谈 C++20 的使用。网上方法,颇为陈旧,看新方式何以优雅实现。 开始之前,定义为先: Comma-separated values (CSV) is a text file format that uses commas to separate values. A CSV file stores tabular data (numbers and text) in plain text, where each line of the file typically represents one data record. Each record consists of the same… Continue Reading Parsing CSV Files in C++20

前两章主要集中于应用实践,理论概念都是蜻蜓点水,本章将重点放在这些概念原理上,深入讲解一下。 宏二段替换 源文件扫描后,宏被替换为目标内容,替换实际上分为两个阶段。 第一阶段的替换发生在参数替换之时。 当宏函数含有参数时,该参数会被替换。只有两种情况例外,一是 token 前面含有 # 或 ## 预处理标记,二是后面紧跟着 ## 预处理标记。 例如: #define Def(x) x #define Function(x, y) x ## y #define Func(x) #x #define Fun(x) ##x // Not allowed #define Fn(x) x# // Not allowed #define Defn(x, y) # x ## y // Not allowed #define A()… Continue Reading 《产生式元编程》第三章 替换蓝染概念纤悉

上篇讲解了 Compile time for,它是遍历技术中的迭代方式之一,特别常用。 它一般与 Fold expressions 组合使用,它们两个,一个相当于编译期的 Range-based for,一个相当于编译期的 for loop。由于 std::index_sequence<N> 本身只是一个静态序列,并不具备向前迭代的能力,而 Fold expressions 天生就是干这个的,两相组合,Compile time for 的功能便可完全具备。 再者,Fold expressions 只适合用来局部访问数据,不支持全局操作,当你想要整体性操作某一数据集合时,除了递归,这种迭代方式是唯一选择。相较起来,其他方式都要更加麻烦,参考上篇的例子。 考虑之前群内讨论过的一个例子,求结构体成员个数,来对比一下几种常见的编译期遍历技术。 下面是一种递归解法: struct Any { template <class T> operator T() const; }; template <class T, class… Args> requires std::is_aggregate_v<T> consteval auto CountAggregateMembers() { if constexpr (requires {… Continue Reading T231119 几种遍历技术实现结构体成员计数的比较

先说明,本篇并不是说 Expansion statements。 遍历方式分为两种,自下而上叫迭代,自上而下叫递归。 递归往往需要多个函数(if constexpr 可以避免),一般迭代是一种更加自然的方式。前面正式文章写过大量 tuple-like 类型的遍历方法,虽然也包含了非 tuple-like 类型的迭代方法,但却是作为次要部分出现的,这篇单独摘出来再讲解一下。 tuple-like 类型就是指存在 std::tuple_size<T> 和 std::get<I>(t) 定义的类型,此类类型具备通用的遍历方式。由旧文可知,目前最简单的迭代方式是借助 std::apply: void pack_iterator_with_tuple(auto&&… args) { auto tuple = std::make_tuple(std::forward<decltype(args)>(args)…); std::apply([](const auto&… tupleArgs) { ((std::cout << tupleArgs << " "), …); }, tuple); } 当然,对于参数包来说,最简单的方式是借助 Fold expressions。 void pack_iterator_with_fold_expr(auto&&… args) { ((std::cout << std::forward<decltype(args)>(args)… Continue Reading T231111 Compile time for

这篇稍微总结一下异构类型的迭代方法。 首先是静态异构类型,主要指的就是 tuple like 类型,它们可以使用 std::apply 迭代。 void print(auto tuple_like_value) { std::apply([](const auto&… args) { (fmt::print("{} ", args), …); }, tuple_like_value); fmt::print("\n"); } int main() { // Iterate std::pair { using heterogenous_t = std::pair<int, double>; heterogenous_t container { 1, 0.8 }; print(container); } // Iterate std::tuple { using heterogenous_t =… Continue Reading T231104 异构类型迭代法