想必大家都听说过「 Java 程序员的吐槽」说低水平的同事写出这种代码:

```java
if(status == A) {
  playwith(A);
}
if(status == B) {
  playwith(B);
}
//...
```

然后这种吐槽的下面还会有不少人煞有介事地分析,说可能是老代码删改留下的痕迹,以前没有一个统一的 `playwith()` 函数云云……

今天很高兴地通知大家,我们 c++,即使是 **新代码,也完全可能** 会写出这种段落,而且这种段落完全 **有意义!** :)
  
  
Make sense 的关键在于, `playwith()`函数 **不再一定能** 在当前的函数上下文调用了。
c++20 引入的 `consteval function` 会阻止传入任何非 `constexpr` 的参数,所以你在一个运行时的 switch context 里是不能用运行期值去调用 `consteval` 函数的:
```cpp
    // 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);
    };
```
```cpp
    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()` 怎么写呢?
—— 没错,只能判断每个可能的编译期值了,于是我们得到了
```cpp
switch(c){case 1: consteval_fn(1);}
```  

  
> [**在 compiler explorer 上查看这个例子**]( https://gcc.godbolt.org/z/6M6T3E1Y8)  
> [例子 on gist]( https://gist.github.com/pnck/eee96898f0d2e332fad5c9b1b0c0676e)  

p.s. consteval 函数编译起来是真的很慢,为了这样一个 **编译期 map** 代价还挺大的
举报· 219 次点击
登录 注册 站外分享
11 条回复  
nooneanyone 小成 2024-5-17 19:09:58
@GeruzoniAnsasu #10 确实,这几天做老项目的代码迁移兼容,c++17+的代码改成 c++11 差点没把握整死了。后来还是打算用 17 编译不过不用高版本的 stl 库,这样来跑在旧环境。
flyqie 小成 2024-4-24 15:17:04
请教下,大家生产环境会用这些看起来。。比较魔法的特性吗。

感觉好像我自己看到的 cpp code 都不太会用这种奇奇怪怪的魔法,写法都很清真。。
ljyst 小成 2024-4-24 00:41:30
这算什么。更底层的写单片机为了节省编译后的程序大小能从 500 字节压到 100+并且不是程序压缩而是手工去做,压缩前的版本单字节变量名或者写法这些自己和圈子老手一看就能看懂
flax5a98aa2 小成 2024-4-23 22:09:43
为什么要把打表写成 consteval 函数,而不是 constexpr 函数啊 o_o ....?想要保证 consteval ,写个新的 consteval 函数,转发参数到写好的 constexpr 函数上就行了。
lostsummer 小成 2024-4-23 18:07:50
实在忍不了。GoLang 中类型断言就是觉得有点丑陋但不得不用的东西,到 C++中能麻烦 3 倍!
cybort 初学 2024-4-23 18:07:38
这相当于编译器给你打表,看着是 consteval_fn(1),其实这里是一个常数,但是写常数看不出是怎么来的。
lxdlam 小成 2024-4-23 17:39:54
本质的核心问题是 C++ 没有真正的 exhaustive 的 enum ,导致编译器无法限定 `E` 的值在一个可优化的 immedate value 范围内,无法将其在 comptime 求出来,所以将其放到参数里无法做编译期生成。

为什么 `to_runtime` 又能 work 了?因为在当前场景下,编译器推导出来对于 `consteval` 函数 `flattern`,可行的值是有限的,所以能够正常编译。把最后一个分支改为 `f(_e)`,编译器会报错 `f` 为一个 `consteval function`,但是参数是 comptime unknown 的。

对于有 exhaustive enum 的语言( rust 、zig 等),要实现这种需求应该是 trivial 的。
MoYi123 小成 2024-4-23 16:08:41
要在编译期算运行期的值只能打表啊, 这也没办法.
InkStone 小成 2024-4-23 15:02:56
这个点很有意思,不过从实用性角度考虑的话感觉不如写个脚本去生成硬编码的代码。
nicaiwss 小成 2024-4-23 14:21:54
@GeruzoniAnsasu 编译期 map 有啥用?能举个例子吗?什么场景会用到?
12下一页
返回顶部