C++ 中文周刊 2025-06-22 第186期

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 753792291 答疑在这里

RSS

欢迎投稿,推荐或自荐文章/软件/资源等,评论区留言

最近有点忙,不过c++26有新进展了


资讯

标准委员会动态/ide/编译器信息放在这里

这里是前方记者Mick发来的报道

欢迎来到C++26的第七次会议,C++26 Wording Freeze is Here! 在半个月后所有提案加入草案之后,新的草案将作为C++26 CD(Committee Draft)刊发,本周期也将正式进入NB Ballot阶段。至此,C++26的所有新特性已经完全确定,剩下的9个月将全部被用在bugfix上。

本次会议共约200人参与,比上次少一些,依然是传统的线下:线上=2:1模式。

在通过的提案方面,本次共通过11篇语言提案和35篇库提案。两者都是本周期的最高值,而且质量一点不差,可以自信的说本次会议是整周期最重要的一次,也是标准变动最大的一次。

语言方面,最最重要的提案,也是全周期最重要的语言提案,自然是P2996静态反射。在这次会议之前,C++的反射能力是很弱的,只能通过TMP魔法去艰难地拿到类的一些有限的信息(比如聚合的成员数量等等)。在一次会议中,C++的反射能力通过P2996和它的4篇辅助提案的通过得到了超级史诗级增强。现在,我们可以直接使用^^T得到T(类型或者值)的反射对象(std::meta::info类型的值),然后通过极为丰富的API去查询之前想都不敢想的各类信息,比如类型名称(identifier_of(^^T)),成员数量(members_of(^^T, …).size()),甚至各类以前用type_traits写的东西现在都可以用API像调用函数一样使用了(比如add_const(^^T )就等价于add_const_t)。除此之外,上述members_of API还给了我们探查任意类的成员数量,每个成员的修饰符/类型/…等等等等…。这只是P2996提供的220个函数的一小部分,但已经足以展示它的巨大力量。这种能力让很多之前需要依赖宏的反射操作(比如enum to string,debug printer)成为几十行的简单程序。

需要注意的是,C++26的静态反射的主要关注点是在探测方面,对生成方面并没有什么能力(比如说不能注入成员函数,不能直接生成变量,…)。这主要是因为主流编译期的架构让编译期注入很难实现,即使是2996中唯一的生成相关函数——被严重限制能力的define_aggregate——也被证明是很难实现的。这个函数会替你完成一个聚合的定义——struct S; define_aggregate(^^S, …);实质上让你程序性的指定S的定义。虽然目前只能生成聚合(没有private成员支持),只能注入成员变量(没有成员函数/using alias/…支持),但是就这么简单的函数支持已经足以让我们实现大量充满想象力的功能了(比如named tuple)

P2996并不是本次会议的终点,4篇辅助提案也都相当重磅。最重要的是P3096函数参数反射,它让我们第一次拥有了探查函数的参数名的手段——parameters_of等API保证了这一点,也让我们得以生成诸如把函数参数打包到一个struct里还保证同样的成员名这类之前想都不敢想的操作。当然,作为一门参数名称可以随便写,两个声明名字可以完全不同的语言,3096也必须面对这一点。提案采取了最简单的做法——如果存在两个声明/定义名称不一致,那么parameters_of就是不合法的。在未来,或许可以推出[[canonical]]属性来标记某个函数的“真正”参数名,不过那就是以后的事情了。

同样重要的是P3394注解。注解本身是一个很简单的提案,一种新的属性[[=xxx]],用开头的等号标记,可以附着在几乎任何地方——类,成员,函数,…。真正重要的是相关的annotations_of API,让我们可以通过这个方法获得库可以阅读的注解。比如说我们可以标记一个类为[[=serializable]],然后库可以自动获取所有标记了这个注解的类然后用反射序列化它们。我们还可以用[[=options(…)]]标记某个成员来指导自动生成命令行参数的第三方库。注解给了我们不可忽略的属性,as they should be!

另外两篇附加提案没有那么重要,但依然是很有价值的附加提案。P3491 define_static_{string,object,array}提供了缺失非持续编译期内存分配的一个临时解决方案:让编译器直接生成一个静态数组。这提供了一定的误用可能(让static段可能变得很大),不过确实是目前解决这一问题的最好方案了。P3293 x.[: base :]则解决了2996的一个小疏忽:subobjects包括成员变量和父类,但是只有前者可以用x.mem语法访问。通过允许x.[: base :]来直接访问父类子对象,我们可以不再需要用两个循环+C cast(因为static_cast无法处理private继承)处理所有子对象。

不在4篇附加提案里的,但是依然和反射紧密相连的特性是P1306 Expansion Statement(template for)。这是一个极度坎坷的提案,曾经被批准进入C++20,但是在最后一刻被没时间了的Core扔出去了,23周期没有任何进展,就在眼看要miss 26的时候终于起死回生,在最后一次会议的最后一天赶上了26的末班车。template for是编译期版本的range-for loop,其执行过程是直接将循环体拷贝N次,而不是生成jmp,从而允许我们写template for (constexpr auto mem : …)这类纯编译期循环语句。虽然本身和反射无关,但是无数的反射程序都因为template for而变得更简单,更容易理解(遍历所有成员终于可以写普通循环了!)

除了反射相关提案,Core这次还通过了constexpr virtual继承这个看上去非常矛盾的提案,不过考虑到constexpr virtual函数已经有了,这也是顺理成章了。此外顺便把预处理器中的所有UB去除了,迈出了focus on安全性的第一步。

标准库方面,通过的提案也毫不逊色。最重磅的是7篇Senders/Receivers补充提案——众所周知,去年通过的S&R框架和C++20 Coroutine一样只提供了最基础的框架内容,而没有提供任何实质性的易用性进步,甚至连个hello world都写不出来。P2079 Parallel Scheduler解决了这一点——为标准提供了第一个标准化的调度器。通过较为宽松的wording给了实现很大的自由度来接入操作系统的调度器,同时避免了直接往标准中加入线程池可能导致的oversubscription问题,并且让P2300的hello world例子终于可以跑通了。除此之外,P3552 std::task则终于补全了Coroutine最基础的库类型之一(另一个是generator,至此终于最重要的两个类型都标准化了),给了一个最最基础的可以使用co_await的类型。task还和S&R框架做了深度融合,真正做到了Senders = Awaitable的承诺。除此之外,P3149 async_scope可谓是S&R基础框架之外最重要的特性,为一直宣传的结构化并发编程打下了坚实的基础,终于不用担心各类生命期问题了。

本次会议历程最坎坷的提案毫无疑问是P2988 optional<T&>。C++17周期std::optional标准化时,就要不要支持引用的optional曾经爆发过激烈的争吵,直接导致一些人心灰意冷去了WG14。主要的争吵点是optional<T&>是不是可以用T*代替,以及其operator=到底应该像原生引用一样assign-through还是像T*一样rebind。争吵是如此激烈以至于最后决定不支持optional<T&>,“留待后人解决”。8年后,随着and_then等便利函数的加入,optional<T&>相对T*的优越性被更多人认知,rebind也形成了共识成为了唯一推荐的实现方案,最终在13个revision之后这一缺失的功能终于重新被C++接纳。下一步就该expected<T&, E&>variant<T&>了吧? 本次会议变动最大的提案无疑是P3179 Parallel Ranges Algorithm。C++17加入了pSTL,为几乎每一个标准STL算法加入了多线程支持(通过第一参数的执行策略,比如std::execution::par_unseq)。但是在C++20 Ranges的大潮中,只有普通的标准算法被Range化了,这些pSTL并没有被关注。现在3179终于解决了这一疏忽,通过将output iterator改为output range同时限制随机访问来解决了相关的概念问题;现在所有的Ranges算法也都可以传入执行策略了。不过还有最后一步没有走完(执行策略和S&R框架的整合),那个就得等以后了。

还有一个重要的优化组合是P3557+P3560,这两篇提案让S&R和反射的报错都通过编译期异常来实现,成为了这个新锐特性向前迈出的一大步。与传统的SFINAE实现相比,编译期异常有着好得多的错误信息和编译速度,在S&R这种极度依赖TMP的框架中真正能发挥出它的作用。希望两者的错误信息不要像Ranges那么难懂吧!

最后还有P2830 type_order_v这个老熟人,在上次会议plenary失败之后,这次卷土重来获得了成功。该type trait终于给了我们编译期一个确定的类型排序手段,能够给出任意两个类型之间的大小关系,对type set等TMP手段很有用。据说S&R的实现有了这个特性减少了几百行…

除了上面提到的所有这些,这次会议还通过了一大堆友好便利的小特性,比如views::indices(indices(n)等价于0, 1, 2, …, n - 1,即iota(0, n)加自动处理类型问题),4篇SIMD补充提案(最重要的一个是终于抛弃了std::datapar::simd这个惨绝人寰的命名,改用std::simd::vec和std::simd::mask;以及simd终于可以当Ranges遍历了),exception_ptr_cast(不调用rethrow而探测exception_ptr内部信息的手段),std::string::subview(终于!返回string_view的substr版本),constexpr shared_ptr,constant_wrapper(更直接的integer_constant,通过template避免写类型名),加入C23标准库特性等等。这绝对是一次收获满满的会议。十分遗憾的是,尽管付出了最大的努力,还是有25篇提案掉了出去,其中不乏重要的(比如let_async_scope和并发队列;它们只能等待C++29了。)

EWG方面,本次会议效率拉满review了54篇提案,通过了包括UB白皮书的最初版(Profile+Implicit Contract+UB Annex组合拳),概念的部分应用(比如range_of<same_as<int>>),让assert()默认调用Contracts,0o八进制字面量等特性。到这个阶段,Stage 2工作组已经开始大量review C++29特性,其中的一些重磅的已经开始有了进展(fiber_context,PM等),对未来充满期望吧!LEWG也不赖,在最后一刻让meta::exception继承了std::exception改正了这个缺憾,研究了constexpr pSTL,并且花了一天时间看zstring_view相关提案来试图提升C API的可操作性和安全性。最后还就std::nontype_t要不要使用constant_wrapper吵了一架(没结果)。他们最重要的通过的C++29提案可能是返回optional<T&>的map::get,终于!不用再忍受operator[]的奇怪行为了。

Stage 1工作组方面,在Nico的多次请求下,SG9终于通过了一个输入版本的views::filter,解决了caching行为带来的多个不直观行为,并且通过了views::null_term(自动生成\0结尾的char Range),views::take_before(前者更通用的版本,指定终止符),组成了第一批C++29适配器。下周期SG9的重点将是解决<numeric>算法的Range化,从而终于迎来可用的ranges::sum。SG7设计了define_aggregate的属性支持,和属性反射一起组成了新一批injection特性,并且通过了consteval value希望一举解决非持续内存分配的主要矛盾。LEWGI则开始了新的重大提案——单位系统框架的review,有希望成为C++29的重大特性之一。

另外还有一些trip report,比如

草药老师

编译器信息最新动态推荐关注hellogcc公众号 本周更新 2025-01-08 第288期

性能周刊

文章

Reflection for C++26!!!

群友ykiko的文章,非常不错

Compiler Explorer: An Essential Kernel Playground for CUDA Developers

godbolt支持nvcc了,链接 https://godbolt.org/z/q6dPT7vsW

如何消费连续范围和连续迭代器

https://mysteriouspreserve.com/blog/2025/06/03/Consume-Contiguous-Iterators-and-Ranges/

感谢yexuanxiao投稿

jemalloc Postmortem

讲了 jemalloc演进历史,虽然现在只是维护态,感兴趣可以看看

Variadic class template arguments

科普文章,直接看代码

#include <tuple>

template <typename... Ts>
class Storage {
   public:
    Storage(Ts&&... args) : elements(args...) {}

    // some getters here

   private:
    std::tuple<Ts...> elements;
};

template <typename... Ts>
Storage(Ts&&... ts) -> Storage<Ts...>;

int main() {
    auto myStore = Storage(41, 4.2, "forty two");
}

经典的overload

template<typename... Ts>
struct Visitor : Ts... {
	using Ts::operator()...;
};

auto v = Visitor{
    [](int i){ std::cout << i << '\n'; },
    [](float f){ std::cout << f << '\n'; },
    [](const std::string& s){ std::cout << s << '\n'; }
};

v(42); // prints 42

另外c++26可能支持可变参数友元函数

template<class... Ts>
class Foo {
  friend Ts...;
};

不用一个一个手写了

Learning about std::as_const, Qt containers and that detach might not mean what you think

qt容器有潜在cow可能性,即使你用const T&也阻止不了潜在的detach/cow

比如这段代码

for(const auto& reg:registrations)

即使循环没改,但是仍然会创建registrastions副本避免竞争

得用std::as_const/qAsConst把registrations包起来才可以

Voronoi, Hashing and OSL

省流,换了个算法

​- 传统哈希函数​使用1997年的Jenkins Lookup3哈希(需位旋转rot和混合mix操作) ​- 性能瓶颈​:3D Voronoi需计算27个相邻网格单元的哈希值(3x3x3),原函数单次计算耗时高

为什么 Windows 还要有 Interlocked 函数,当我们已经有 std::atomic 了?

std::atomic来得太晚,以前的东西也还能用,就留着了

Snapshot Analysis for C++

想法挺好

不过我在github上见到过魔改gdb/jemalloc来支持分析的。比较简单,他这个功能差很多

Not a template error after all…

template<class ...Args >
QSqlQuery exec_sql(const QString& sql, Args... args)
{
    QSqlQuery query;
    query.prepare(sql);
    Q_ASSERT(query.boundValues().size() == sizeof...(args));
    bind_value(query,0, args...);
    if(!query.exec() && query.lastError().isValid())
        qWarning() << query.lastError().text()<< query.lastQuery();
    return query;
}

Qassert报错,但是却没检查出sql有啥问题,实际是是prepare报错,导致后面assert报错,掩盖了真实的问题

修复就是给prepare加上判断

template<class ...Args>
QSqlQuery exec_sql(const QString& sql, Args... args) {
    QSqlQuery query;
    if (!query.prepare(sql)) {
        qWarning() << "PREPARE FAIL:" << query.lastError().text() 
                   << "| SQL:" << sql
                   << "| Driver:" << query.driver()->lastError().text();
    } else {
        Q_ASSERT(query.boundValues().size() == sizeof...(args));
        bind_value(query, 0, args...);
        if (!query.exec() && query.lastError().isValid())
            qWarning() << "EXEC FAIL:" << query.lastError().text();
    }
    return query;
}

Type-based vs Value-based Reflection

对比c++ 26反射和以前的反射ts设计,比较两种设计方案。还算有意思。感兴趣可以深入读一下

简单总结

方面 类型反射(TS)​ 值反射(C++26)​
实体表示 类型(reflexpr(E)生成独特类型) 值(^^E生成std::meta::info
编程模型 模板元编程(函数式,不可变) 常规编程(指令式,可使用变量和循环)
代码复杂度 高(需模板技巧和外部库如Boost.Mp11) 低(使用标准库算法和lambda)
可读性 低(大量模板和限定符) 高(类似普通C++代码)
递归处理 需通过模板递归或mp_all_of 直接递归调用函数
条件分支 if constexpr和模板保护 直接使用if或逻辑运算符
成员访问语法 obj.*get_pointer_v(不支持位域) obj.[:r:](支持所有成员类型)

C++26: 禁止绑定临时对象的引用返回值

收紧用法,挺好的,一下代码c++26后不合法 ill-formed

auto&& f1() {
    return 42;  // ill-formed
}
const double& f2() {
    static int x = 42;
    return x;   // ill-formed
}

工作招聘,微信群友招人

微信大模型训练研发工程师(工程 Infra工作)

岗位职责:

base北京, 各职级都有

有自荐或者推荐 群友欢迎滴滴我~ 备注训练框架jd。+微信+yep_cs 或者发简历到lucasbai@tencent.com

岗位要求:

推理也有岗位~

微信大模型推理研发工程师(工程 Infra工作)

岗位职责:

base北京, 各职级都有

备注推理jd。+微信+yep_cs 或者发简历到lucasbai@tencent.com

岗位要求:

然后也招业务开发

工作

要求:


上一期

本期

下一期

看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持! 觉得写的不错那就给点吧, 在线乞讨 微信转账