constexpr 的來龍去脈
constexpr 是什麼?
關鍵字 constexpr (constant expression) 是在 C++11 中引入的,並且在 C++14 中進行了優化。
constexpr 和 const 一樣可以用來修飾變數:試圖修改 constexpr 變數時,編譯器將會報錯。
不同於 const, constexpr 還可以修飾函數和類的構造函數。 constexpr 表示值或者返回值是常量,並且如果可能,在編譯時計算它們。
一個 constexpr 整型值能夠用在任何 const 整型值可以用的地方,例如模板參數和數組的申明。
當值在編譯時計算而不是運行時計算時,它能夠使程式運行得更快,並使用更少的記憶體。
為了限制編譯時常量計算的複雜性,以及其對編譯時間潛在的影響, C++14 標準需要 constexpr 類型必須為字面值類型。
語法
constexprliteral-type identifier = constant-expression ;
constexprliteral-type identifier { constant-expression } ;
constexprliteral-type identifier ( params ) ;
constexprctor ( params ) ;
參數
params
一個或者多個參數, 每個參數必須是字面值類型並且本身是 常量表達式 。
返回值
一個 constexpr 變數或者函數必須返回字面值類型。
constexpr 變數
constexpr 和 const 的主要區別是 const 變數的初始化可以被延時到運行時,而 constexpr 變數必須在編譯時初始化。所有的 constexpr 變數都是 const。
-
當變數由字面值類型初始化時,能夠聲明為
constexpr。如果初始化時由構造函數執行的,那麼該構造函數也必須聲明為constexpr。 -
引用類型可以聲明為
constexpr只要滿足:引用對象是由 常量表達式初始化的,並且初始化時任何隱式轉換也是常量表達式。 -
所有
constexpr變數或者函數的聲明必須擁有constexpr說明符。
舉例:
constexpr float x = 42.0;
constexpr float y{108};
constexpr float z = exp(5, 3);
constexpr int i; // Error! Not initialized
int j = 0;
constexpr int k = j + 1; //Error! j not a constant expression
constexpr 函數
constexpr 函數的返回值是在編譯時計算的。調用程式碼需要返回值在編譯時初始化一個 constexpr 變數, 或者提供一個非類型模板參數。 當參數是 constexpr 值時,constexpr 函數產生一個編譯時的常量。當被調用時傳入非 constexpr 參數,或者其返回值在非編譯時請求,constexpr 函數和普通函數一樣將產生提個運行時的值。(這種行為能夠讓你避免編寫兩個相同功能的函數,一個為 constexpr 版本,一個為非 constexpr 版本)
constexpr 函數或者構造函數默認是 inline 的.
以下規則適用於 constexpr 函數:
- 必須接受並返回字面值類型。
- 可以是遞歸的.
- 不能是虛構的。當類有虛基類時,構造函數不能定義為
constexpr。 - 函數體可以定義為
= default或者= delete。 - 函數體不能包含
goto語句或者try塊。 - 顯式特化/具體化(explicit specialization)的非
constexpr模板能夠聲明為constexpr。 - 顯式特化/具體化(explicit specialization)的非
constexpr模板不必聲明為constexpr。
以下 constexpr 函數規則適用於 Visual Studio 2017 及以後的版本:
- 可以包含
if和switch語句,以及所有循環語句包括for,while和 do-while - 可以包含初始化的局部變數,並且必須是字面值類型,不能是
static或者 thread-local。該局部變數不必是const的。 - 非
static的constexpr成員函數不必隱式為const
constexpr float exp(float x, int n)
{
return n == 0 ? 1 :
n % 2 == 0 ? exp(x * x, n / 2) :
exp(x * x, (n - 1) / 2) * x;
}
提示:
在 Visual Studio 調試器中, 當調試一個非優化的調試版本,你能夠通過在函數內部設置斷點來區分
constexpr函數是否在編譯時計算。如果斷點能夠觸發,則為運行時計算,否則,為編譯時計算。
示例
以下例子演示了 constexpr 變數, 函數, 以及用戶自定義類型。 在 main() 最後, constexpr 成員函數 GetValue() 是在運行時調用的,因為其返回值沒有被要求在編譯時確定。
#include <iostream>
using namespace std;
// Pass by value
constexpr float exp(float x, int n)
{
return n == 0 ? 1 :
n % 2 == 0 ? exp(x * x, n / 2) :
exp(x * x, (n - 1) / 2) * x;
}
// Pass by reference
constexpr float exp2(const float& x, const int& n)
{
return n == 0 ? 1 :
n % 2 == 0 ? exp2(x * x, n / 2) :
exp2(x * x, (n - 1) / 2) * x;
}
// Compile-time computation of array length
template<typename T, int N>
constexpr int length(const T(&)[N])
{
return N;
}
// Recursive constexpr function
constexpr int fac(int n)
{
return n == 1 ? 1 : n * fac(n - 1);
}
// User-defined type
class Foo
{
public:
constexpr explicit Foo(int i) : _i(i) {}
constexpr int GetValue() const
{
return _i;
}
private:
int _i;
};
int main()
{
// foo is const:
constexpr Foo foo(5);
// foo = Foo(6); //Error!
// Compile time:
constexpr float x = exp(5, 3);
constexpr float y { exp(2, 5) };
constexpr int val = foo.GetValue();
constexpr int f5 = fac(5);
const int nums[] { 1, 2, 3, 4 };
const int nums2[length(nums) * 2] { 1, 2, 3, 4, 5, 6, 7, 8 };
// Run time:
cout << "The value of foo is " << foo.GetValue() << endl;
}
constexpr 和 const 的異同
相同處:
constexpr 和 const 一樣可以用來修飾變數:試圖修改 constexpr 變數時,編譯器將會報警。一個 constexpr 整型值能夠用在任何 const 整型值可以用的地方,例如模板參數和數組的申明。
不同處:
constexpr 還可以修飾函數和類的構造函數。constexpr** 表示值或者返回值是常量,並且如果可能,在編譯時計算它們。
當一個值在編譯時計算而不是運行時計算時,它能夠使程式運行得更快,並使用更少的記憶體。
所有的 constexpr 對象都是 const 的,但不是所有的 const 對象都是 constexpr 的。
示例
int sz; // non-constexpr variable
…
constexpr auto arraySize1 = sz; // error! sz's value not
// known at compilation
std::array<int, sz> data1; // error! same problem
constexpr auto arraySize2 = 10; // fine, 10 is a
//compile-time constant
std::array<int, arraySize2> data2; // fine, arraySize2
// is constexpr
int sz; // as before
…
const auto arraySize = sz; // fine, arraySize is
// const copy of sz
std::array<int, arraySize> data; // error! arraySize's value
// not known at compilation
應該使用 constexpr 的場景
只要允許,儘可能使用 constexpr,當值在編譯時計算而不是運行時計算時,它能夠使程式運行得更快,並使用更少的記憶體。
不應該使用 constexpr 的場景
constexpr 是對象或者函數介面的一部分,所以如果你使用了 constexpr 但反悔了,移除 constexpr 可能會導致大量的調用程式碼編譯失敗。(比如添加 I/O 操作用於調試或者性能調優可能導致這樣的問題,因為 I/O 語句通常不是在 constexpr 函數中執行的。)
引用
-
Microsoft Docs (//docs.microsoft.com/en-us/cpp/cpp/constexpr-cpp?view=msvc-160)
-
Effective Modern C++

