Re: [问题] constexpr 使用的时机

楼主: Feis (永远睡不着 @@)   2015-05-23 23:34:52
推 kao50126: 推!另外对函式部分也有兴趣 05/12 21:25
constexpr 用在函式上的意义跟变量上相同,也就是 constexpr 所指示的函式其回传
值 "可能" 可以在编译期算出并当做常数用。[补充1]
在这里最奥妙的地方就是用到了 “可能”这个说词。但为了避免太过着重于解释设计
的细节,这里从实际的使用情境来说明。
将 constexpr 用在函式上想达到的目的与下列技巧相关: "宏" (macro)、TMP (
template metaprogramming) 和 inline 指示符。
constexpr 用在函式上的时机基本上就是为了简化、明确化或一般化这些相关技巧。
我们这里就举个求最大值的例子:
做法1: 一般函式
int Max(int a, int b) {
return a > b ? a : b;
}
int main() {
int m1 = Max(3, 4);
constexpr int m2 = Max(3, 4); // [编译失败] 一般函式的呼叫不预期会在编译期
// 算出 (非常数表示式)
return 0;
}
做法2: inline 函式
inline int Max(int a, int b) {
return a > b ? a : b;
}
int main() {
int m1 = Max(3, 4);
constexpr int m2 = Max(3, 4); // [编译失败] inline 函式的呼叫不预期会在编
// 译期算出 (非常数表示式)
return 0;
}
这两种做法都无法在语法上当作编译期常数用,也就是说 int a[Max(3, 4)] 在
C++14 前的标准中是不合法的。
而如果了解 inline 的精神就知道上述两种做法原则上没什么差异,最后是否 inline
的决定权还是在编译器。
不管函式 inline 与否,都跟函式的值可否在编译期算出的 "语意" 无关。也就是说函
式加不加 inline 的考量并不是要表示该函式值能在编译期算出与否,所以对编译器来
说这些函式呼叫都不会是常数表示式 (也就是不以为他会在编译器算出)
上述两种做法的 Max(3, 4) 虽然在语法上无法做为编译期常数使用,但是编译器依然
有可能在编译期因最佳化将 Max(3, 4) 算出后用 4 取代。也就是它是个编译期可算出
来但是语法上无法当作编译期常数使用的例子。
做法3: 宏
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main() {
int m1 = MAX(3, 4);
constexpr int m2 = MAX(3, 4);
return 0;
}
宏虽然无法保证其值可以在编译期能算出,但是如果宏的内容在套用引数后是个常
数表示式,其值就可以当编译期常数用。
例如这里的
constexpr int m2 = MAX(3, 4);
在套用后会变成
constexpr int m2 = ((3) > (4) ? (3) : (4));
此时, 因为等号右边是可以在编译期算出的常数表示式,因此 m2 的宣告是合法的。
虽然宏在这个例子可以运作,但是在 C++ 中使用宏有许多诸如可视范围或型别安
全等缺点。再来宏技术上要做出递回等函式使用的效果,做法相当复杂而没弹性。
做法4: TMP
template<int A, int B>
struct Max {
enum { Value = (A > B) ? A : B };
};
int main() {
int m1 = Max<3, 4>::Value;
constexpr int m2 = Max<3, 4>::Value;
return 0;
}
TMP 的做法是唯一可以保证函式值 "会" 在编译期算出的。
以之前的宏例子来说,m1 的值虽然 "可以" 在编译期算出,但不一定 "会" 算出
来。因为常数表示式的值虽然可以在编译期算出,但是否会算出来要看使用情境与编译
器需要决定。
相反地,在使用 TMP 的做法中, m1 的值肯定是会在编译期算出来的。只是把这个弹
性留给编译器也不见得是比较糟的选择。
至于 TMP 最大的缺点很明显的就是难懂、难写又难用。
[感谢 azureblaze 补充 TMP 的缺点是无法在执行期用同样的方式呼叫,要写两份。]
做法 5: constexpr 函式
constexpr int Max(int a, int b) {
return a > b ? a : b;
}
int main() {
int m1 = Max(3, 4);
constexpr int m2 = Max(3, 4);
return 0;
}
constexpr 函式的优点很明显: 写起来跟一般函式几乎无异,却可能可以当作编译期常
数使用。
换句话说,int a[Max(3, 4)] 在这个例子中会是合法的。
constexpr 函式跟一般函式的主要差别就是在语意上会跟编译器表示其值有可能在编译
期算出。所以当用在需要编译期常数的地方时,编译器就会去确认该值是否真的能在编
译期算出 [注1]。
constexpr 函式相较宏和 TMP 的优点主要在于容易使用且安全,同时也比较符合设
计师的原意,让编译器可以比较容易理解设计师意图。
换句话说,mconstexpr 函式的使用原则就是当你因某种原因想要用宏或 TMP 去取代
一个函式值的计算,那你就可以考虑看看使用 constexpr 函式是否能达成你想要的目
的。
使用 constexpr 函式的优点相较于宏跟 TMP 通常比较多,但需要小心的地方在于如
果使用的情境不要求其值是编译期常数时,函式是否有加上 constexpr 指示符几乎没
差。
注1: 实际上还有很多要求,主要的精神就是该函式的运算不会影响非常数以外的部
分。也就是你算那个函式产生的效应跟你直接改成常数是一样的。
PS: 为了简化说明,有些地方有点宽松的带过。此外小弟在实务上使用经验很少,有甚
    么思考不周的地方还请指教
补充1: 这里补充一下前面第一段文字中所谓的 "可能" 表示该函式所有有可能的呼叫
中,要存在至少一种可以在编译期算出值的呼叫才合乎语意。
[感谢 azureblaze 提醒]
补充2: 因为感觉有点长跟乱, 所以我将上面这个例子的讨论写成一个表格:
一般函式 inline 函式 宏 TMP constexpr 函式
作者: kao50126 (无从)   2014-05-12 21:25:00
推!另外对函式部分也有兴趣
作者: azureblaze (AzureBlaze)   2015-05-23 23:42:00
应该说当constexpr函数的参数全是constexpr时结果“必须”能在编译期算出来c++11为了确保这点规范超严几乎只能一行 return constant_expression;而已c++14就放宽了很多可以加流程控制可是也变成编译器要在编译期真的跑你的函数
楼主: Feis (永远睡不着 @@)   2015-05-23 23:48:00
constexpr 的参数是常数表示式时必须能算出这点有点疑惑我对于标准的认知没有这点, 想请教一下这个说法来源 ?
作者: azureblaze (AzureBlaze)   2015-05-23 23:52:00
我觉得用字让对编译器的规范和对coder的规范有点混淆coder写的code必须能在编译期算出结果编译器“可能”会使用编译期的结果标准对函数内容的规范没明讲,可是他的目标是这样
楼主: Feis (永远睡不着 @@)   2015-05-23 23:55:00
a 大的意思是 constepxr int f() 这样的函式只能回传固定值吗?阿. 这例子不好. 我再想想
作者: azureblaze (AzureBlaze)   2015-05-24 00:01:00
他主要解决的问题是macro或TMP可以在编译期算可是却不能在执行期算,必须准备两个版本constexpr在编译期或直行其都能用同一份code得到结果
楼主: Feis (永远睡不着 @@)   2015-05-24 00:04:00
macro 不能在执行期 "算" 阿? 嗯. 我想想这意思但是 constexpr 本质上不保证可以在编译期算, 即使引数是常数表示式.. 如果我没误会的话.
作者: azureblaze (AzureBlaze)   2015-05-24 00:05:00
喔 macro可以,只是大家都恨macro
楼主: Feis (永远睡不着 @@)   2015-05-24 00:11:00
我稍微看了一下当初的 proposal: http://goo.gl/M3h8zD不确定 a 大指的目标是哪个大段 ?
作者: azureblaze (AzureBlaze)   2015-05-24 00:18:00
http://ideone.com/0A86Pc 我错了,确实是用到了才检查符合“一定能算出”的叫core constant expression编译期需要的是这种,普通的则没这么严查了一下7.1.5.5说至少要有一组参数能得到编译期结果不过no diagnostic required.
楼主: Feis (永远睡不着 @@)   2015-05-24 00:42:00
确实. 我觉得 a 大讲的是理想上将常数表示式函式化时比较好的设计. 7.1.5.5 的内容在 C++14 又有所修正我主观觉得考量编译器能力上, 现在的 constexpr 是妥协的结果.

Links booklink

Contact Us: admin [ a t ] ucptt.com