前面介紹了模板這種編譯期動作,關于編譯期動作,有必要介紹下constexpr。
在這之前有必要簡單提一下constexpr與const的關系,兩者字面上都表達常量的意思。
主要的區別是:const修飾的變量可以在運行時才初始化,而constexpr則一定會在編譯期初始化。
constexpr才是名符其實的常量,所有的constexpr都是const。
而const表示的是read only的語義,保證修飾的變量運行時不可以更改,如果直接改動它,編譯器在編譯時會報錯。const修飾的變量可以在運行時才初始化,而constexpr則一定會在編譯期初始化。
有人可能會用指針等騷操作來修改const修飾的變量值,這種情況下,CPP標準規定產生的是未定義行為,具體可能不同編譯器的具體行為會不相同。所以騷操作魔改const后,無論產生什么行為,都沒必要奇怪,也沒必要深究。
下面具體介紹下constexpr。
如上所述,constexpr修飾的才是真正的常量,它會在編譯期間計算出來,整個運行過程中都不可被改變。
constexpr還可用于修飾函數,這個函數的返回值會盡可能在編譯期間被計算出來,然后作為一個常量,但是如果編譯期間不能被計算出,此函數就是被當作一個普通函數處理。
如何使用constexpr?
這里我直接貼出cppreference中的示例代碼:
#include <iostream>
#include <stdexcept>
// C++11 constexpr functions use recursion rather than iteration
// (C++14 constexpr functions may use local variables and loops)
constexpr int factorial(int n) { return n <= 1 ? 1 : (n factorial(n - 1)); }
// literal class
class conststr {
const char p;
std::size_t sz;
public:
template <std::size_t N>
constexpr conststr(const char (&a)[N]) : p(a), sz(N - 1) {}
// constexpr functions signal errors by throwing exceptions
// in C++11, they must do so from the conditional operator ?:
constexpr char operator[](std::size_t n) const { return n < sz ? p[n] : throw std::out_of_range(""); }
constexpr std::size_t size() const { return sz; }
};
// C++11 constexpr functions had to put everything in a single return statement
// (C++14 doesn't have that requirement)
constexpr std::size_t countlower(conststr s, std::size_t n = 0, std::size_t c = 0) {
return n == s.size() ? c : 'a' <= s[n] && s[n] <= 'z' ? countlower(s, n + 1, c + 1) : countlower(s, n + 1, c);
}
// output function that requires a compile-time constant, for testing
template <int n>
struct constN {
constN() { std::cout << n << '\n'; }
};
int main() {
std::cout << "4! = ";
constN<factorial(4)> out1; // computed at compile time
volatile int k = 8; // disallow optimization using volatile
std::cout << k << "! = " << factorial(k) << '\n'; // computed at run time
std::cout << "the number of lowercase letters in \"Hello, world!\" is ";
constN<countlower("Hello, world!")> out2; // implicitly converted to conststr
}
可以大體觀察到constexpr的語法如下:
constexpr literal-type identifier = constant-expression ;
constexpr literal-type identifier { constant-expression } ;
constexpr literal-type identifier ( params ) ;
constexpr ctor ( params ) ;
通過示例代碼及相關注釋,就可以看到,能在編譯期做constexpr就會優先在編譯期計算,編譯期不行就在運行時計算。
也可以看到,在C++14之前constexpr修飾函數時不能有if-else for循環等語句,而在C++14后,這個問題有了改善。
那什么情況下應該使用constexpr修飾函數?
不在乎編譯時間的話,盡可能用constexpr修飾所有的函數,大家有時間可以看看cpp的源碼,多數成員函數都是使用的constexpr修飾。
思考題
constexpr有一個條件是需要滿足literal type,那literal type究竟是什么類型?
推薦閱讀:
https://docs.microsoft.com/en-us/cpp/cpp/constexpr-cpp?view=msvc-170
參考資料
https://en.cppreference.com/w/cpp/language/constexpr