# 常量表達式(constexpr)
常量表達式機制是為了:
* 提供一種更加通用的常量表達式
* 允許用戶自定義的類型成為常量表達式
* 提供了一種保證在編譯期完成初始化的方法(可以在編譯時期執行某些函數調用)
考慮下面這段代碼:
```
enum Flags { good=0, fail=1, bad=2, eof=4 };
constexpr int operator|(Flags f1, Flags f2)
{ return Flags(int(f1)|int(f2)); }
void f(Flags x)
{
switch (x) {
case bad: /* … */ break;
case eof: /* … */ break;
case bad|eof: /* … */ break;
default: /* … */ break;
}
}
```
在這里,常量表達式關鍵字constexpr表示這個重載的操作符“|”只應包含形式簡單的運算,如果它的參數本身就是常量 ,那么這個操作符應該在編譯時期就應該計算出它的結果來。(譯注: 我們都知道,switch的分支條件要求常量,而使用constexpr關鍵字重載操作符“|”之后,雖然“bad|eof”是一個表達式,但是因為這兩個參數都是常量,在編譯時期,就可以計算出它的結果,因而也可以作為常量對待。)
除了可以在編譯時期被動地計算表達式的值之外,我們希望能夠強制要求表達式在編譯時期計算其結果值,從而用作其它用途,比如對某個變量進行賦值。當我們在變量聲明前加上constexpr關鍵字之后,可以實現這一功能,當然,它也同時會讓這個變量成為常量。
```
constexpr int x1 = bad|eof; // ok
void f(Flags f3)
{
// 錯誤:因為f3不是常量,所以無法在編譯時期計算這個表達式的結果值
constexpr int x2 = bad|f3;
int x3 = bad|f3; // ok,可以在運行時計算
}
```
使用constexpr強制在運行期求值,一般用于全局對象(或namespace內的對象),尤其是那些放在只讀區的對象。
除了基本類型外,對于那些構造函數比較簡單的對象和由其構成的表達式,也可以成為常量表達式
```
struct Point {
int x,y;
constexpr Point(int xx, int yy) : x(xx), y(yy){}
};
constexpr Point origo(0,0);
constexpr int z = origo.x;
constexpr Point a[] = {Point(0,0), Point(1,1), Point(2,2) };
constexpr int x = a[1].x; // x 變成常量1
```
需要注意的是,constexpr并不是const的通用版,反之亦然:
* const主要用于表達“對接口的寫權限控制”,即“對于被const修飾的量名(例如const指針變量),不得通過它對所指對象作任何修改”。(但是可以通過其他接口修改該對象)。另外,把對象聲明為const也為編譯器提供了潛在的優化可能。具體來說就是,如果把一個量聲明為const,并且沒有其他地方對該量作取址運算,那么編譯器通常(取決于編譯期實現)會用該量的實際常量值直接替換掉代碼中所有引用該量的地方,而不用在最終編譯結果中生成對該量的存取指令。
* constexpr的主要功能則是讓更多的運算可以在編譯期完成,并能保證表達式在語義上是類型安全的。(譯注:相比之下,C語言中#define只能提供簡單的文本替換,而不具任何類型檢查能力)。與const相比,被constexpr修飾的對象則強制要求其初始化表達式能夠在編譯期完成計算。之后所有引用該常量對象的地方,若非必要,一律用計算出來的常量值替換。
(譯注:zwvista的一段評論,有助于我們理解constexpr的意義,感謝zwvista。constexpr 將編譯期常量概念延伸至括用戶自定義常量以及常量函數,其值的不可修改性由編譯器保證,因而constexpr 表達式是一般化的,受保證的常量表達式。)
參考:
* the C++ draft 3.6.2 Initialization of non-local objects, 3.9 Types [12], 5.19 Constant expressions, 7.1.5 The constexpr specifier
* [N1521=03-0104] Gabriel Dos Reis: [Generalized Constant Expressions](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1521.pdf) (original proposal).
* [N2235=07-0095] Gabriel Dos Reis, Bjarne Stroustrup, and Jens Maurer: [Generalized Constant Expressions — Revision 5](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2235.pdf) .
(翻譯:陳良喬,感謝:zwvista)
- C++11 FAQ中文版 - C++11 FAQ
- Stroustrup先生關于中文版的授權許可郵件
- Stroustrup先生關于C++11 FAQ的一些說明
- 關于C++11的一般性的問題
- 您是如何看待C++11的?
- 什么時候C++0x會成為一部正式的標準呢?
- 編譯器何時將會實現C++11標準呢?
- 我們何時可以用到新的標準庫文件?
- C++0x將提供何種新的語言特性呢?
- C++11會提供哪些新的標準庫文件呢?
- C++0x努力要達到的目標有哪些?
- 指導標準委員會的具體設計目標是什么?
- 在哪里可以找到標準委員會的報告?
- 從哪里可以獲得有關C++11的學術性和技術性的參考資料?
- 還有哪些地方我可以讀到關于 C++0x的資料?
- 有關于C++11的視頻嗎?
- C++0x難學嗎?
- 標準委員會是如何運行的?
- 誰在標準委員會里?
- 實現者應以什么順序提供C++11特性?
- 將會是C++1x嗎?
- 標準中的"concepts"怎么了?
- 有你不喜歡的C++特性嗎?
- 關于獨立的語言特性的問題
- __cplusplus宏
- alignment(對齊方式)
- 屬性(Attributes)
- atomic_operations
- auto – 從初始化中推斷數據類型
- C99功能特性
- 枚舉類——具有類域和強類型的枚舉
- carries_dependency
- 復制和重新拋出異常
- 常量表達式(constexpr)
- decltype – 推斷表達式的數據類型
- 控制默認函數——默認或者禁用
- 控制默認函數——移動(move)或者復制(copy)
- 委托構造函數(Delegating constructors)
- 并發性動態初始化和析構
- noexcept – 阻止異常的傳播與擴散
- 顯式轉換操作符
- 擴展整型
- 外部模板聲明
- 序列for循環語句
- 返回值類型后置語法
- 類成員的內部初始化
- 繼承的構造函數
- 初始化列表
- 內聯命名空間
- Lambda表達式
- 用作模板參數的局部類型
- long long(長長整數類型)
- 內存模型
- 預防窄轉換
- nullptr——空指針標識
- 對重載(override)的控制: override
- 對重載(override)的控制:final
- POD
- 原生字符串標識
- 右角括號
- 右值引用
- Simple SFINAE rule
- 靜態(編譯期)斷言 — static_assert
- 模板別名(正式的名稱為"template typedef")
- 線程本地化存儲 (thread_local)
- unicode字符
- 統一初始化的語法和語義
- (廣義的)聯合體
- 用戶定義數據標識(User-defined literals)
- 可變參數模板(Variadic Templates)
- 關于標準庫的問題
- abandoning_a_process
- 算法方面的改進
- array
- async()
- atomic_operations
- 條件變量(Condition variables)
- 標準庫中容器方面的改進
- std::function 和 std::bind
- std::forward_list
- std::future和std::promise
- 垃圾回收(應用程序二進制接口)
- 無序容器(unordered containers)
- 鎖(locks)
- metaprogramming(元編程)and type traits
- 互斥
- 隨機數的產生
- 正則表達式(regular expressions)
- 具有作用域的內存分配器
- 共享資源的智能指針——shared_ptr
- smart pointers
- 線程(thread)
- 時間工具程序
- 標準庫中的元組(std::tuple)
- unique_ptr
- weak_ptr
- system error