摘要訊息 : C++ 14 提出了緩和對 constexpr 函式的要求.

0. 前言

C++ 11 引入了 constexpr 關鍵字, 用於標識一個變數或者函式. 對於變數來說, 它就是常數表達式; 對於函式來說, 編碼器有機會讓函式直接在編碼期完成計算. 但是 C++ 11 對於 constexpr 的限制實在是太嚴格了, 特別是對於 constexpr 函式.

C++ 14 的提案 N3652 《Relaxing constraints on constexpr functions》這篇提案旨在緩和這些約束, 讓 constexpr 函式在日常程式設計中更加常見.

更新紀錄 :

  • 2022 年 4 月 16 日進行第一次更新和修正.

1. 提案內容

在 C++ 11 中, 一個具有 constexpr 標識的函式只允許出現

  • 空的陳述式;
  • static_assert;
  • typedef 以及 using, 但是不能使用它們定義一個類別或者列舉;
  • 在非建構子中, 必須有一個 return 陳述式.

N3652 提出, 允許下面內容出現在函式中 :

  • 允許 constexpr 函式中宣告變數, 但是變數不可以是未初始化的, static 的和 thread_local 的;
  • 允許 constexpr 函式中存在條件陳述式 if 以及 switch;
  • 允許 constexpr 函式中存在迴圈 for (包含 Range-For), while 以及 do-while;
  • 允許生命週期在常數表達式計算之內的變數在 constexpr 函式中發生改變;
  • 允許 constexpr 函式的回傳值為 void, 並且函式中可以不出現 return 陳述式 (void 在 C++ 11 中非字面值型別, 但是在 C++ 14 中成為字面值型別).
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);
    }
}

Code 1 中的每一行程式碼都只能在 C++ 14 做到, 包括其不存在 return 陳述式.

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

  • 函式不可以是虛擬函式;
  • 回傳值對應的型別必須是一個字面值型別;
  • 每一個參數對應的型別都屬於字面值型別;
  • 函式中不能出現 __asm, __asm__, static, thread_local, goto 以及 try-catch 區塊.

除此之外, 類別的建構子如果要被 constexpr 標識, 那麼在 C++ 14 中還需要遵守下面的限制 :

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

2. 總結

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