
想必大家都听说过「 Java 程序员的吐槽」说低水平的同事写出这种代码:
if(status == A) { playwith(A); } if(status == B) { playwith(B); } //... 然后这种吐槽的下面还会有不少人煞有介事地分析,说可能是老代码删改留下的痕迹,以前没有一个统一的 playwith() 函数云云……
今天很高兴地通知大家,我们 c++,即使是 新代码,也完全可能 会写出这种段落,而且这种段落完全 有意义! :)
Make sense 的关键在于, playwith()函数 不再一定能 在当前的函数上下文调用了。 c++20 引入的 consteval function 会阻止传入任何非 constexpr 的参数,所以你在一个运行时的 switch context 里是不能用运行期值去调用 consteval 函数的:
// this lambda is conteval auto flatten = [](E _e) consteval { auto [id, extra] = static_map(_e); auto [name, info] = std::move(extra); return make_tuple(static_cast<int>(_e), id, name, info); }; cout << std::apply( [](auto &&...args) { return std::format("in case {}, id {} (name:{}) has info: {}\n", args...); }, // flatten(FOO)); // work, but only with literal to_runtime(flatten, e)); // work at runtime 那这个 to_runtime() 怎么写呢? 没错,只能判断每个可能的编译期值了,于是我们得到了
switch(c){case 1: consteval_fn(1);} p.s. consteval 函数编译起来是真的很慢,为了这样一个 编译期 map 代价还挺大的
1 FlashEcho 2024-04-23 14:14:38 +08:00 没太理解这么做的意思 consteval 函数最好是比较简单的单纯计算,所以我会声明它为 consteval 希望能在编译器搞完 如果需要传入任何非 constexpr 的参数了,说明这个函数需要依赖复杂的上下文,为什么还要用 consteval ?在编译期又不一定能提速,开发麻烦,速度还不一定快 |
2 GeruzoniAnsasu OP @chesha1 consteval 函数可以方便实现复杂的编译期 map ,但要在运行时查取这个 map 就会回到 switch 运行时值 case 编译期值的情况,然后 case CONST f(CONST) 就「看起来比较蠢但这里是 c++」 |
3 nicaiwss 2024-04-23 14:21:54 +08:00 via iPhone @GeruzoniAnsasu 编译期 map 有啥用?能举个例子吗?什么场景会用到? |
4 InkStone 2024-04-23 15:02:56 +08:00 这个点很有意思,不过从实用性角度考虑的话感觉不如写个脚本去生成硬编码的代码。 |
5 MoYi123 2024-04-23 16:08:41 +08:00 要在编译期算运行期的值只能打表啊, 这也没办法. |
6 lxdlam 2024-04-23 17:39:54 +08:00 本质的核心问题是 C++ 没有真正的 exhaustive 的 enum ,导致编译器无法限定 `E` 的值在一个可优化的 immedate value 范围内,无法将其在 comptime 求出来,所以将其放到参数里无法做编译期生成。 为什么 `to_runtime` 又能 work 了?因为在当前场景下,编译器推导出来对于 `consteval` 函数 `flattern`,可行的值是有限的,所以能够正常编译。把最后一个分支改为 `f(_e)`,编译器会报错 `f` 为一个 `consteval function`,但是参数是 comptime unknown 的。 对于有 exhaustive enum 的语言( rust 、zig 等),要实现这种需求应该是 trivial 的。 |
7 lxdlam 2024-04-23 17:43:08 +08:00 @lxdlam 补一句,通过 template + if constexpr + static_assert 可能可以将这个 `to_runtime` 自动化生成出来,不过我也好久没写过 C++ 了,如果不行当我没说。 |
8 cybort 2024-04-23 18:07:38 +08:00 via Android 这相当于编译器给你打表,看着是 consteval_fn(1),其实这里是一个常数,但是写常数看不出是怎么来的。 |
9 lostsummer 2024-04-23 18:07:50 +08:00 实在忍不了。GoLang 中类型断言就是觉得有点丑陋但不得不用的东西,到 C++中能麻烦 3 倍! |
10 GeruzoniAnsasu OP |
11 flax5a98aa2 2024-04-23 22:09:43 +08:00 为什么要把打表写成 consteval 函数,而不是 constexpr 函数啊 o_o ....?想要保证 consteval ,写个新的 consteval 函数,转发参数到写好的 constexpr 函数上就行了。 |
12 GeruzoniAnsasu OP @flax5a98aa2 我(们)离最佳实践还很远,较真来说。 写 c++代码与其它语言最大的区别是,经常要从一大堆实现方法中 filter 出一个标准,而不是突破语言的限制尽可能地让程序灵活起来。 所以 我不知道。 在确定 consteval 真的很有用之前我也宁愿普通地打表,更何况过去的那些代码生成器也不会给你生成新写法的代码。 |
13 ljyst 2024-04-24 00:41:30 +08:00 这算什么。更底层的写单片机为了节省编译后的程序大小能从 500 字节压到 100+并且不是程序压缩而是手工去做,压缩前的版本单字节变量名或者写法这些自己和圈子老手一看就能看懂 |
14 flyqie 2024-04-24 15:17:04 +08:00 via Android 请教下,大家生产环境会用这些看起来。。比较魔法的特性吗。 感觉好像我自己看到的 cpp code 都不太会用这种奇奇怪怪的魔法,写法都很清真。。 |
15 nooneanyone 2024-05-17 19:09:58 +08:00 @GeruzoniAnsasu #10 确实,这几天做老项目的代码迁移兼容,c++17+的代码改成 c++11 差点没把握整死了。后来还是打算用 17 编译不过不用高版本的 stl 库,这样来跑在旧环境。 |