在 C++20 裡,以 const
為字首的關鍵字總共有四個:const
、constexpr
、consteval
、constinit
;其中,const
應該是大家都熟系的老標準就有的,constexpr
則是在 C++11 的時候加入的;consteval
和 constinit
則是 C++20 的新東西。
這四種 const
到底差別是什麼呢?這邊參考《const vs constexpr vs consteval vs constinit in C++20》這篇文章、稍微整理一下這四種 const 的差別。
const
最基本的 const
,代表的是「常數」(constant),代表是不可修改的;下面就是一個簡單的例子:
const int a = 1; a = 2; // error
因為變數 a
在宣告時有加上 const
,所以之後都不能去修改他的值。
而另一種用法,則是用在成員函式上,禁止修改成員資料;不過這邊就先跳過了。
雖然 const 代表這個值不會有變化,但是它不代表變數會是在編譯階段(compile-time)就完成處理,所以雖然在整數的時候可以當作編譯階段的變數、但是其他型別的變數的時候就不行了。
下面就是一個例子:
const int count = 3; std::array<double, count> doubles{ 1.1, 2.2, 3.3 }; // but not double: const double dCount = 3.3; std::array<double, static_cast<int>(dCount)> moreDoubles{ 1.1, 2.2, 3.3 }; // error: the value of 'dCount' is not usable in a constant expression
前面的 count
雖然可以拿來作為 array
的 template 引數,但是後面的 static_cast<int>(dCount)
卻會出現編譯錯誤。
像是這種要求在編譯階段就有明確的值、或是結果的式子,在 C++ 裡面是被稱為「constant expressions」(參考),最常見的就是這種 non-type template arguments、或是陣列的大小。
constexpr
constexpr
是在 C++11 時加入的(CppReference),它代表的是宣告的變數或函式是可以在編譯階段完成計算的。
像上面的例子就可以修改成:
constexpr double dCount = 3.3; std::array<double, static_cast<int>(dCount)> doubles2{ 1.1, 2.2, 3.3 };
這樣就可以正確編譯了。
而如果把它用在函式上的話,則會變成一個可以在編譯階段(compile-time)執行、也可以在執行階段(runtime)執行的函式。
像在下面的例子裡面,square()
這個函式就可以同時在 compile-time 和 runtime 的狀況下執行:
#include <iostream> #include <array> constexpr int square(const int val) { return val * val; } int main(int argc, char** argv) { // compile time const int count = 3; std::array<double, square(2)> doubles; // runtime int v = 3; int r = square(v); }
但是這邊也要注意,如果一個函式被標記是 constexpr
的話,那也需要確定它的內容都能在編譯階段就被計算完;如果裡面有用到不是在編譯階段就決定的內容的話,那在惠沒辦法在編譯階段執行的。
下面就是一個例子:
#include <iostream> #include <array> int i = 10; constexpr int get_val(const int val) { return val * i; } int main(int argc, char** argv) { std::array<int, get_val(2)> a; //error }
上面的例子裡面,由於 get_val()
有用到 i
這個一般的全域變數,所以無法在編譯階段完成,這也導致在宣告 std::array<>
的時候會出現編譯錯誤。
但是由於 constexpr
也允許有 runtime 的版本,所以如果是去呼叫他的 runtime 版本、像是 int v = get_val(2)
的話,則是可以正確編譯的。
而實際上,Heresy 之前在《在 header 檔使用 constexpr 定義全域變數》這篇文章也有簡單介紹過,constexpr
也還可以拿來更方便地定義全域變數,有興趣的話也可以看看。
此外,if constexpr()
這種在編譯階段就完成的條件判斷,在很多時候也是很好用的!
所以如果要大量使用 template 這類在編譯階段就決定數值的寫法的話,那 constexpr
會是相當有幫助的!
consteval
consteval
是在 C++20 加入的(CppReference),他和 constexpr
的不同在於他只能用來宣告函式、不能用在變數上;同時,他會明確地限制函式僅能在編譯階段執行。
像上面的例子,如果把 square()
前面的 constexpr
改成 consteval
的話,就是下面的樣子:
#include <iostream> #include <array> consteval int square(const int val) { return val * val; } int main(int argc, char** argv) { // compile time const int count = 3; std::array<double, square(2)> doubles; // runtime int v = 3; int r = square(v); // error }
這樣的話,「int r = square(v);
」就因為 v
不是 compile-time 就決定的變數、而會出現編譯錯誤了。
constinit
constinit
也是在C++20 加入的(CppReference);相較於 consteval
只能用在函式上,constinit
則是只能用在靜態(static)和 thread-local 變數上,它的意義是強制變數要在編譯階段完成初始化(constant initialization、參考)。
而比較有趣的是,他雖然是以「const」開頭,但在使用他宣告變數的時候,實際上並沒有 const
不可修改的特性。
下面就是一個例子:
#include <iostream> constexpr int square(const int val) { return val * val; } constinit int v = square(2); int main(int argc, char** argv) { v = 3; }
也因為這邊的 v
不是常數,所以儘管它是在編譯階段就完成初始化的,但是也不能拿來作為 template 引數;像是這邊如果寫 std::array<int, v>
就會出現編譯錯誤。
而如果希望變數有 const
不可修改的特性的話,應該就是直接回去用 constexpr
就可以了。
大概就是這樣了,要整理的話,大概就是:
const
:可用在變數、成員函式,代表不可修改、但是不保證是編譯階段完成constexpr
:可用在變數、函式,代表不可修改、可以在編譯階段完成,另外還可以搭配if
使用consteval
:只可用在函式,代表函式必須要在編譯階段完成constinit
:只可用在 static 和 thread-local 變數、代表變數要在編譯階段完成初始化,變數之後可以修改。
不過老實說,個人是不知道 constinit
什麼時候有用啦…