看板 C_and_CPP 關於我們 聯絡資訊
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 函式 ------------------------------------------------------------------------- 當編譯期常數用 不能 不能 可能 一定 可能 在編譯期算出值 可能 可能 可能 一定 可能 在執行期作計算 可以 可以 可以 不能 可以 * 可以發現 constexpr 在效果上比較接近巨集。也許從更簡單安全使用的巨集來理解 會是個方向 (?) -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 140.122.83.198 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1432395296.A.399.html
azureblaze: 應該說當constexpr函數的參數全是constexpr時 05/23 23:42
azureblaze: 結果「必須」能在編譯期算出來 05/23 23:43
azureblaze: c++11為了確保這點規範超嚴 05/23 23:44
azureblaze: 幾乎只能一行 return constant_expression;而已 05/23 23:45
azureblaze: c++14就放寬了很多可以加流程控制 05/23 23:46
azureblaze: 可是也變成編譯器要在編譯期真的跑你的函數 05/23 23:47
Feis: constexpr 的參數是常數表示式時必須能算出這點有點疑惑 05/23 23:48
Feis: 我對於標準的認知沒有這點, 想請教一下這個說法來源 ? 05/23 23:49
azureblaze: 我覺得用字讓對編譯器的規範和對coder的規範有點混淆 05/23 23:52
azureblaze: coder寫的code必須能在編譯期算出結果 05/23 23:53
azureblaze: 編譯器「可能」會使用編譯期的結果 05/23 23:53
azureblaze: 標準對函數內容的規範沒明講,可是他的目標是這樣 05/23 23:54
Feis: a 大的意思是 constepxr int f() 這樣的函式只能回傳固定值 05/23 23:55
Feis: 嗎? 05/23 23:55
Feis: 阿. 這例子不好. 我再想想 05/23 23:56
azureblaze: 他主要解決的問題是macro或TMP可以在編譯期算 05/24 00:01
azureblaze: 可是卻不能在執行期算,必須準備兩個版本 05/24 00:01
azureblaze: constexpr在編譯期或直行其都能用同一份code得到結果 05/24 00:02
Feis: macro 不能在執行期 "算" 阿? 嗯. 我想想這意思 05/24 00:04
Feis: 但是 constexpr 本質上不保證可以在編譯期算, 即使引數是 05/24 00:05
Feis: 常數表示式.. 如果我沒誤會的話. 05/24 00:05
azureblaze: 喔 macro可以,只是大家都恨macro 05/24 00:05
Feis: 我稍微看了一下當初的 proposal: http://goo.gl/M3h8zD 05/24 00:11
Feis: 不確定 a 大指的目標是哪個大段 ? 05/24 00:12
azureblaze: http://ideone.com/0A86Pc 我錯了,確實是用到了才檢查 05/24 00:18
azureblaze: 符合「一定能算出」的叫core constant expression 05/24 00:24
azureblaze: 編譯期需要的是這種,普通的則沒這麼嚴 05/24 00:25
azureblaze: 查了一下7.1.5.5說至少要有一組參數能得到編譯期結果 05/24 00:35
azureblaze: 不過no diagnostic required. 05/24 00:35
Feis: 確實. 我覺得 a 大講的是理想上將常數表示式函式化時 05/24 00:42
Feis: 比較好的設計. 7.1.5.5 的內容在 C++14 又有所修正 05/24 00:43
Feis: 我主觀覺得考量編譯器能力上, 現在的 constexpr 是妥協的結 05/24 00:44
Feis: 果. 05/24 00:44
※ 編輯: Feis (140.122.83.198), 05/24/2015 16:24:34