| |
首页 淘股吧 股票涨跌实时统计 涨停板选股 股票入门 股票书籍 股票问答 分时图选股 跌停板选股 K线图选股 成交量选股 [平安银行] |
股市论谈 均线选股 趋势线选股 筹码理论 波浪理论 缠论 MACD指标 KDJ指标 BOLL指标 RSI指标 炒股基础知识 炒股故事 |
商业财经 科技知识 汽车百科 工程技术 自然科学 家居生活 设计艺术 财经视频 游戏-- |
天天财汇 -> 设计艺术 -> constexpr是否是过度设计?既然条件是已知,算法在设计之初都要考虑到为什么不直接填结果? -> 正文阅读 |
|
[设计艺术]constexpr是否是过度设计?既然条件是已知,算法在设计之初都要考虑到为什么不直接填结果? |
[收藏本文] 【下载本文】 |
在学习C++ constexpr中遇到一个问题,既然使用constexpr模板和元编程要求 入参是编译前已经确定,那为什么不直接考虑完所有的可能的场景… |
fmt::format("{:3.01f}", value) 以上这句代码可以在编译期解析格式化字符串,生成数据校验代码。当格式化字符串错误,或者value无法被该方式格式化时自动报错。 现在代码里平均100行就有一条这类格式化日志,整个工程上千条日志。 全程手写校验,请。 |
说实话我没 get 到题主的点,所以回答可能也不完全准确。 有些东西是确定的,但不代表就适合自己算,比如阶乘 n!" role="presentation">n!n! 就没有查表和遍历之外的好方法, gcd(a,b)" role="presentation">gcd(a,b)\gcd(a, b) 需要做一个循环,如果是反三角函数之类的就更不适合自己算了。代码的超参数可能会变化,每次变化就要自己重新算然后复制粘贴肯定是不如编译器直接算的。所谓“考虑完所有的情形”纯粹是完美的想象,谁能保证自己真的能考虑完所有的情形,谁能保证几个月之后还要不要复用这段代码。 退一步讲,我们可以用 variadic template 实现循环展开,但是此时循环的上下界、步长必须是编译期间可知的量,比如给定常量 n" role="presentation">nn ,上界是 (n2)" role="presentation">(n2)\dbinom{n}{2} ,如果你手动维护 (n2)" role="presentation">(n2)\dbinom{n}{2} 的计算(外部计算然后复制过来),就有 n" role="presentation">nn 和 (n2)" role="presentation">(n2)\dbinom{n}{2} 取值不一致的潜在危险,毕竟谁都有可能粗心忘记改某个数值。 能几乎不改变什么 C++ 语法就实现编译期化简,自然也没必要额外引入其他的什么预处理,而且 constexpr 函数接收非 constexpr 参数时也能放到运行期计算,泛用性也好,一个定义处理所有情形。 我不是做 C++ 的,对于 C++23 也不熟悉,但我知道 Rust 里有类似的现象:编译期间可以将 JSON 文件的内容当作静态常量处理,本质也是编译期间读取文件内容,而非编译完之后在 runtime 读取文件,相当于省去从 JSON 里把数据复制粘贴到代码里的步骤,这种情况如果能够实现,也是非常方便设置超参数的。 |
C++发明constexpr函数是在C++11,而当初由于极度保守(考虑到它是十五年前发明的,那时候Windows7才发布),本身只是试水而已。到了C++17,发明constexpr if之后,C++标准实际上已经重新确定了constexpr函数的定位:编译期计算,例如我之前写的寻找deque最佳块大小的函数:
这个函数需要你输入deque的value_type的大小,返回合适的块大小,函数的调用发生在deque成员函数的内部,就算可以算出来大小,你也没办法填进deque的成员函数里,并且因为deque的value_type的大小可以是任意大,你也不可能去在代码里用一张几亿行的表去枚举它的结果。 到了C++20时期,委员会逐渐认为仅仅做编译期计算已经满足不了胃口了,因此继续扩展constexpr函数的应用范围,constexpr函数不仅可以编译期用,还要运行期用,这样可以避免维护两套(一套编译期一套运行期)代码,同时纯编译期计算划分给consteval函数了。因此在C++23时,标准几乎允许任何函数标记为constexpr(除了协程),同时C++20、C++23、C++26也逐渐给几乎所有标准库函数添加了constexpr,扫清了维护一套代码的阻碍。 |
你这纯粹是想太多。 为什么会有constexpr函数?不就是人们发现一些运行期的计算能够放到编译期,以节省运行期时间。 你想把源码里的所有constexpr函数结果都算出来,再填进去?你乐意做这些重复性工作,当然是没问题的。一旦函数的实现修改,你得把所有结果重新算,再填进去。 |
假设我需要自定义一个array类,它有类型参数T,一个长度参数int。
初始化的时候,我得知道需要多大的空间,例如我需要Array<int, 20>,那我就需要80字节,如果是Array<Double, 30>,那么需要240字节。 注意这里的T是类型参数,你根本不知道真实使用的时候到底是什么,用户给你来个Array<Panda, 100000>完全有可能,但是你根本不清楚这个Panda到底是啥。 那怎么知道我要申请多大的内存呢? 使用int size = sizeof(T) * S,然后调用malloc当然是可以的(有时可能不好用new,因为你不知道类型T有没有重载new操作符)。 但是这个语句可能会生成个乘法指令对吧。 所以constexpr int size = sizeof(T) * S,把这个运算放到编译期里面去,可能会更好一些。 |
为了代码的可读性和可复用性。常量确实是要求编译期已知,所以这个人确实可以让人预先算出来,但是如果计算过程复杂或者计算结果反人类,那么constexpr就是一个好选择。 比如说,我的代码有一个参数是某个时间的时间戳,也就是从时间元年到现在的秒数,但是这个时间戳经常会因为这份代码在不同位置要发生改动,都是编译期的常量,都可以通过计算得到,但是这个计算过程反人类。比如我有个同事需要调用我的接口,他看见我传递了一个魔鬼数字,搁那研究半天才发现是一个时间戳,又花费了半天去计算出来了他需要的时间点的时间戳去填这个常量(包括但不限于手动计算,写简易程序计算,在线时间戳工具装换)等等。 上面这个场景的问题还包括,当前只是一个标准的时间戳,如果场景变更,我的代码改成了某个时间戳再减去一个固定值,如果我不写注释,估计神明也难找到这个魔鬼常数的规律,这个接口将以无比反人类行为被人唾弃。 constexpr的存在就可以解决或者缓解这种反人类行为。例如我可以直接定义一个constexpr函数,把一串格式化的时间字符直接装化成这个参数,这样下个开发者读到这里时,很容易就能发现这个参数的来龙去脉,调用也会很银杏化! 这个常量计算最初可以追溯到c语言的define手动代码内联替换,不过如果你同时也是一名c语言开发者,你会发现define的缺点明显。首先是不可调试,如果报错在某个魔法宏里面,神仙也得抓脑壳,让本就不富裕的发量雪上加霜!其次是设计复杂和条件苛刻,包括但不限于不能指定类型,递归设计复杂,不能换行导致需要用很多个 \ 去增强可读性,甚至还会因此无法获得IDE智能感知,代码补全的支持。 宏这玩意在编译器报错前,没人能知道它的小毛病,在报错后除了设计者没人愿意去修它的bug。毕竟人不是编译器,即使是VS这种能辅助展开宏的银杏化工具展开的宏代码我都不想去读,对齐梦魇,人脑模拟演算替换。如果出错了让你去改,就像极了一坨屎放你餐盘里,你还得硬着头皮吃下去。。。 常量表达式的应用还有很多,比如著名的fmt库的格式化解析,比如还某些复杂公式的常量值如定点,某个中间值均可以转化成银杏化便于人眼识别的语句等等,真可谓是给码农续命的一大利器。 |
穷举出所有结果列出来是目的,不是手段啊。 constexpr consteval 的目的是 : 自动 "在需要使用constexpr模板和元编程的结果的地方直接填已经算好的结果" 你手填填到猴年马月了: 4 个参数类型的组合就有 4X3X2X1 =24种, 函数体里如果有8个if branch, 那就24*8, 如果每个if branch 再套2层每层 3个 ,那就 24 X8X3. 这还每考虑到 那些可以计算不可返回的 编译期类型比如 std::vector 和 constexpr placement new带来的结果阶乘式增加。 constexpr consteval 的目的确实是打表 是 直接填已经算好的结果, 它是实现这些目的的手段。 你的思考角度 有限制: 你只考虑了 最终结果是一个 【简单值】的情况。 这是单点。 实际编译期计算和编译期推导主要解决的是一类2维的问题:
阶乘结果是2维的,穷举就已经很大了。 而加入编译期for-loop 和 while 之后, 是个3维的。编译期的可使用不可返回的可变容器比如std::vector 性质和for-loop类似 也是一个独立维度(当然实现时不可能老实展开,可以利用编译期解释)。 compile-time-placement-new 性质也类似更复杂一些因为分配器有控制逻辑,控制逻辑本身也是一个维度。 实际上还可能是4维的,如果算上 & | &&| const| noexcept 这个specifier维度。 编译期展开的本质确实是算一个笛卡尔积,但是这东西容易爆炸,所以必需有工具(constexpr consteval)辅助(编译过程中一些维度是可以被剪枝略过的),手写是绝对不现实的。 你可以考虑一个简单的2维表 行是状态,列是 可变因素组合,行列交叉是 下一个状态。 整个东西确实是全部可以手填的,但是你想想它的数量级,即使是一个只有50行的小函数,上百个状态还是有的,不靠工具你手写不出的,人要是能手填这个,那计算机根本就没有存在的必要,人脑就算出来了。 |
|
[收藏本文] 【下载本文】 |
上一篇文章 下一篇文章 查看所有文章 |
|
|
股票涨跌实时统计 涨停板选股 分时图选股 跌停板选股 K线图选股 成交量选股 均线选股 趋势线选股 筹码理论 波浪理论 缠论 MACD指标 KDJ指标 BOLL指标 RSI指标 炒股基础知识 炒股故事 |
网站联系: qq:121756557 email:121756557@qq.com 天天财汇 |