最近发现的 C++ 笑话: switch(a){ case 1: return 1;}

148 天前
 GeruzoniAnsasu

想必大家都听说过「 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);}

在 compiler explorer 上查看这个例子
例子 on gist

p.s. consteval 函数编译起来是真的很慢,为了这样一个 编译期 map 代价还挺大的

3355 次点击
所在节点    C++
15 条回复
chesha1
148 天前
没太理解这么做的意思

consteval 函数最好是比较简单的单纯计算,所以我会声明它为 consteval 希望能在编译器搞完

如果需要传入任何非 constexpr 的参数了,说明这个函数需要依赖复杂的上下文,为什么还要用 consteval ?在编译期又不一定能提速,开发麻烦,速度还不一定快
GeruzoniAnsasu
148 天前
@chesha1 consteval 函数可以方便实现复杂的编译期 map ,但要在运行时查取这个 map 就会回到 switch 运行时值 case 编译期值的情况,然后 case CONST f(CONST) 就「看起来比较蠢但这里是 c++」
nicaiwss
148 天前
@GeruzoniAnsasu 编译期 map 有啥用?能举个例子吗?什么场景会用到?
InkStone
148 天前
这个点很有意思,不过从实用性角度考虑的话感觉不如写个脚本去生成硬编码的代码。
MoYi123
148 天前
要在编译期算运行期的值只能打表啊, 这也没办法.
lxdlam
148 天前
本质的核心问题是 C++ 没有真正的 exhaustive 的 enum ,导致编译器无法限定 `E` 的值在一个可优化的 immedate value 范围内,无法将其在 comptime 求出来,所以将其放到参数里无法做编译期生成。

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

对于有 exhaustive enum 的语言( rust 、zig 等),要实现这种需求应该是 trivial 的。
lxdlam
148 天前
@lxdlam 补一句,通过 template + if constexpr + static_assert 可能可以将这个 `to_runtime` 自动化生成出来,不过我也好久没写过 C++ 了,如果不行当我没说。
cybort
148 天前
这相当于编译器给你打表,看着是 consteval_fn(1),其实这里是一个常数,但是写常数看不出是怎么来的。
lostsummer
148 天前
实在忍不了。GoLang 中类型断言就是觉得有点丑陋但不得不用的东西,到 C++中能麻烦 3 倍!
GeruzoniAnsasu
148 天前
@lostsummer

你好,有种东西叫 typescript

而且 c++的类型体操其实是越发展越简单的,新标准一直在引入能简化旧 trick 的写法 —— 除了我还未能一窥真容的 executors
flax5a98aa2
148 天前
为什么要把打表写成 consteval 函数,而不是 constexpr 函数啊 o_o ....?想要保证 consteval ,写个新的 consteval 函数,转发参数到写好的 constexpr 函数上就行了。
GeruzoniAnsasu
148 天前
@flax5a98aa2 我(们)离最佳实践还很远,较真来说。

写 c++代码与其它语言最大的区别是,经常要从一大堆实现方法中 filter 出一个标准,而不是突破语言的限制尽可能地让程序灵活起来。

所以 —— 我不知道。 在确定 consteval 真的很有用之前我也宁愿普通地打表,更何况过去的那些代码生成器也不会给你生成新写法的代码。
ljyst
148 天前
这算什么。更底层的写单片机为了节省编译后的程序大小能从 500 字节压到 100+并且不是程序压缩而是手工去做,压缩前的版本单字节变量名或者写法这些自己和圈子老手一看就能看懂
flyqie
147 天前
请教下,大家生产环境会用这些看起来。。比较魔法的特性吗。

感觉好像我自己看到的 cpp code 都不太会用这种奇奇怪怪的魔法,写法都很清真。。
nooneanyone
124 天前
@GeruzoniAnsasu #10 确实,这几天做老项目的代码迁移兼容,c++17+的代码改成 c++11 差点没把握整死了。后来还是打算用 17 编译不过不用高版本的 stl 库,这样来跑在旧环境。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1034939

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX