Re: [問題] constexpr 使用的時機
推
05/12 21:25,
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 函式
-------------------------------------------------------------------------
當編譯期常數用 不能 不能 可能 一定 可能
在編譯期算出值 可能 可能 可能 一定 可能
在執行期作計算 可以 可以 可以 不能 可以
* 可以發現 constexpr 在效果上比較接近巨集。也許從更簡單安全使用的巨集來理解
會是個方向 (?)
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 140.122.83.198
※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1432395296.A.399.html
→
05/23 23:42, , 1F
05/23 23:42, 1F
→
05/23 23:43, , 2F
05/23 23:43, 2F
→
05/23 23:44, , 3F
05/23 23:44, 3F
→
05/23 23:45, , 4F
05/23 23:45, 4F
→
05/23 23:46, , 5F
05/23 23:46, 5F
→
05/23 23:47, , 6F
05/23 23:47, 6F
→
05/23 23:48, , 7F
05/23 23:48, 7F
→
05/23 23:49, , 8F
05/23 23:49, 8F
→
05/23 23:52, , 9F
05/23 23:52, 9F
→
05/23 23:53, , 10F
05/23 23:53, 10F
→
05/23 23:53, , 11F
05/23 23:53, 11F
→
05/23 23:54, , 12F
05/23 23:54, 12F
→
05/23 23:55, , 13F
05/23 23:55, 13F
→
05/23 23:55, , 14F
05/23 23:55, 14F
→
05/23 23:56, , 15F
05/23 23:56, 15F
→
05/24 00:01, , 16F
05/24 00:01, 16F
→
05/24 00:01, , 17F
05/24 00:01, 17F
→
05/24 00:02, , 18F
05/24 00:02, 18F
→
05/24 00:04, , 19F
05/24 00:04, 19F
→
05/24 00:05, , 20F
05/24 00:05, 20F
→
05/24 00:05, , 21F
05/24 00:05, 21F
→
05/24 00:05, , 22F
05/24 00:05, 22F
→
05/24 00:11, , 23F
05/24 00:11, 23F
→
05/24 00:12, , 24F
05/24 00:12, 24F
→
05/24 00:18, , 25F
05/24 00:18, 25F
→
05/24 00:24, , 26F
05/24 00:24, 26F
→
05/24 00:25, , 27F
05/24 00:25, 27F
→
05/24 00:35, , 28F
05/24 00:35, 28F
→
05/24 00:35, , 29F
05/24 00:35, 29F
→
05/24 00:42, , 30F
05/24 00:42, 30F
→
05/24 00:43, , 31F
05/24 00:43, 31F
→
05/24 00:44, , 32F
05/24 00:44, 32F
→
05/24 00:44, , 33F
05/24 00:44, 33F
※ 編輯: Feis (140.122.83.198), 05/24/2015 16:24:34
討論串 (同標題文章)
本文引述了以下文章的的內容:
完整討論串 (本文為第 3 之 4 篇):