潮.C++: constexpr constructor, constexpr operator overloading

TJSW
5 min readMar 23, 2019

--

constexpr 的用法中我們提過一個 C++ 函式只要滿足一些簡單的限制,現代 C++ 編譯器就可以幫你在編譯期算出函式的結果。而我們一直沒有提到的類別的 constructor 以及類別的 member function 成員函式甚至是 operator overloading 運算子重載,他也是一個函式啊!是不是也能加上 constexpr 修飾呢?

答案當然是可以,不然這篇就不用寫了。

那麼具體來說 constexpr 加在類別的成員函式們可以幹嘛?就是希望我們開發 者自定義的類別也可以宣告成 constexpr 變數,達到編譯期的運算效果。

#include <cstdio>constexpr int sq(int n) { return n * n; }constexpr CClass cube(const CClass &c) { return c * c * c; }int main()
{
constexpr CClass c1(sq(2));
constexpr CClass c2_6 = cube(c1);
constexpr CClass c3_6 = cube(CClass(sq(3)));
static_assert(c2_6.n_ == 64, "!!!");
static_assert(c3_6.n_ == 729, "!!!");
}

如果一切順利的話,我們應該要可以算出 c2_6 代表 2 的 6 次方,c3_6 代表 3 的 6 次方,並且最後通過 printf 印出來。

我們觀察一下上面的半殘代碼:

  1. 上面的 cube() 涉及了對 CClass 物件的「乘法」,很明顯這裡我們需要實作 CClass 的乘法 operator overloading。
  2. cube() 還是一個標上 constexpr 的函式,代表了裡面的運算元 (c),以及運算 (*) 都要是編譯時期可知的操作,也就是 c * c * c 必須是一個 constant expression
  3. c1 也掛上 constexpr,因此他的初始化操作也必須是一個 constexpr function。

結上所述,我們必須對 CClass 的 constructor,以及 operator*() 加上 constexpr 的修飾。

一個 constructor 畢竟代表了整個類別的性質,掛上 constexpr 的 constructor 就代表使用者自定義的類別是一個字面型別 (literal type)。

因此整個類別被拿用初始化 constexpr 變數時 (例如 main()c1) ,必須確保除了 constexpr constructor 裡面沒有奇怪的操作,而且所有的成員變數都要被初始化列表 (member initialization list) 以編譯時期可知的值初始化。(或是成員宣告直接賦值也行)

完整的 CClass 定義應該是這樣的:

class CClass
{
public:
constexpr explicit CClass(int n) : n_(n) {}
constexpr CClass operator*(const CClass &rhs) const {
return CClass(n_ * rhs.n_);
}
int n_;
};
  1. CClass 的 constructor 完全沒有操作,也初始化了所有,也是唯一的成員變數 n_,因此自然就符合了 constexpr 的限制。(可以試試看把 n_ 寫在 constructor 函式體裡面賦值,就會吃 compile error 了)
  2. operator*() 就是個成員函式,做了一個 constexpr 物件回來,也很乖巧。

所以我們再度利用組語編譯,也能發現編譯器的確把 c2_6c3_6 在編譯時期就算出來了。

.section __TEXT,__const
.p2align 2 ## @_ZZ4mainE2c1
__ZZ4mainE2c1:
.long 4 ## 0x4
.p2align 2 ## @_ZZ4mainE4c2_6
__ZZ4mainE4c2_6:
.long 64 ## 0x40
.p2align 2 ## @_ZZ4mainE4c3_6
__ZZ4mainE4c3_6:
.long 729 ## 0x2d9

也可以不要編譯期算

constexpr CClass c2_6 = cube(c1);
constexpr CClass c3_6 = cube(CClass(sq(3)));
CClass c88(88);
static_assert(c2_6.n_ == 64, "!!!");
static_assert(c3_6.n_ == 729, "!!!");
printf("%d %d %d\n", c2_6.n_, c3_6.n_, c88.n_);

就像 constexpr function 一樣,根據我們在什麼時候使用,就可以編譯期算,或是執行期才算。比如上面這個 c88,他就不會是一個在 TEXT 段的常數。而是執行期呼叫 printf() 的時候才把 n_ 從 register 裡面拿出來壓進 stack 裡呼叫 printf()

今天的內容很簡單,就是再把 constexpr 的適用範圍再擴展,同學們可以試著更改增減看看 CClass 的各個定義看有什麼結果 XD。

--

--