摘要訊息 : C++ 14 Paper N3652《Relaxing constraints on constexpr functions》導讀

C++ 14 Paper N3652 :《Relaxing constraints on constexpr functions》

C++ 11 引入了 constexpr 關鍵字, 用於標識一個具有編碼期計算能力的變數或者函式, 編碼器可以根據是否有這個標識, 將某些計算在編碼期直接完成. 但是 C++ 11 對於 constexpr 的限制實在是太嚴格了, 特別是對於 constexpr 函式. 這篇 Paper 旨在緩和這些約束, 讓 constexpr 函式在日常程式設計中更加常見

在 C++ 11 中, 一個具有 constexpr 標識的函式或者建構子中, 只能存在以下陳述式 :

  • 空的陳述式
  • static_assert
  • typedef 以及 using, 但是不能定義一個類別或者列舉
  • 必須有一個 return 陳述式

在這份 Paper 中, 開頭就提出了放鬆這些約束, 讓更多的陳述式加入 constexpr 函式 :

  • 允許 constexpr 函式中宣告變數, 但是變數不可以是 未初始化的、static 的和 thread_local
  • 允許 constexpr 函式中存在條件陳述式 if 以及 switch, 但是不可以出現 goto
  • 允許 constexpr 函式中存在迴圈 for (包含 range-based for)、while 以及 do-while
  • 允許生命週期在常數表達式計算之內的變數在 constexpr 函式中發生改變

除此之外, 這篇 Paper 還針對字面值型別作出了改變, 即增加了 void 型別為字面值型別

那麼在 C++ 11 中不可以被 constexpr 標識且回傳型別為 void 的函式在 C++ 14 中可以被 constexpr 標識

通過上述的總結, 我們可以寫出如下的程式碼 :

constexpr void func() noexcept {
    if(1 == 2) {

        while(1 < 2) {

            switch(false) {

                default:

                    break;

            }

        }

    }

    int arr[] {1, 2, 3, 4, 5, 6, 7};

    arr[2] = 10;

    for(auto &value : arr) {

        do {

            ++value;

        }while(false);

    }

}

上述程式碼的每一行, 都只能在 C++ 14 做到, 包括這個函式沒有 return 陳述式

然而, 儘管 C++ 14 放鬆了對於 constexpr 函式的約束, 但是對於 constexpr 仍然還是要遵循以下的限制 :

  1. 函式不可以是虛擬函式
  2. 回傳值對應的型別必須是一個字面值型別
  3. 每一個參數對應的型別都屬於字面值型別
  4. 函式可以被 = default 或者 = delete 標識
  5. 函式中不能出現 __asm__asm__staticthread_localgoto 以及 try-catch 區塊

對於 constexpr 建構子來說, 除了遵循上述的規則之外, 還需要遵循以下的規則 :

  • 類別不可以擁有虛擬的基礎類別
  • 每一個非易變的非靜態成員變數和基礎類別中的物件都需要被初始化
  • 如果類別是一個非空的 union 類別或者包含不具名非空等位成員的非等位類別, 那麼每一個非靜態成員變數都應該被初始化
  • 每一個非靜態成員以及基礎類別物件的初始化所使用的建構子也應該被 constexpr 所標識

 

放鬆了 constexpr 對於函式的約束, 這將帶來非常多的好處. 例如我們可以直接對符合條件的函式標識 constexpr, 讓編碼器幫我們判斷這個函式是否具有編碼期計算的能力. 在 C++ 11 中, 如果我們想達到編碼期計算的效果, 必須使用 Template Meta-Programming

現在 C++ 2a 階段, 我們可以看向標準樣板程式庫中, 好多演算法函式都被 constexpr 所標識, 甚至是出現了 Constexpr Iterator

對於 constexpr 的態度, 我們應該儘量學習並且接受它, 畢竟它是非常有用的特性. 如果一個函式或者變數適合被 constexpr 所標識, 那麼我們應該儘量對其進行標識, 讓編碼器幫我們執行優化