天天看點

constexpr和常量表達式常量表達式constexpr變量

常量表達式

常量表達式(const expression)是指值不會改變并且在編譯過程就能得到計算結果的表達式。顯然,字面值屬于常量表達式,用常量表達式初始化的const對象也是常量表達式。

const int a = 3;//a是常量表達式
const int b = a+1;//b是常量表達式
int c = 8;//c不是常量表達式,因為c的資料類型是int而不是const int
const int d = get_size();//d不是常量表達式,因為d的值要到運作時才能擷取到           

字面值類型

常量表達式的值需要在編譯時就得到計算,是以對聲明constexpr時用到的類型必須有所限制。因為這些類型一般比較簡單,值也顯而易見、容易得到,就把它們稱為字面值類型(literal type)。

字面值類型的概念由Bjarne Stroustrup提出,644号議題(2008.2)将字面值概念的概念定義如下:

    A type is a literal type if it is:

a scalar type; or

a class type with

·a trivial copy constructor,

·a trivial destructor,

·a trivial default constructor or at least one constexpr constructor other than the copy constructor,

all non-static data members and base classes of literal types; or

an array of literal type.

大意是:

一個字面值類型應具有如下特點

是一個标量類型(如整型、浮點型、實體類型和枚舉類型)

或是一個具有如下特征的類:

·一個平凡的拷貝構造函數

·一個平凡的析構函數

·一個預設構造函數或者至少一個constexpr類型的構造函數(除拷貝構造函數外),所有非靜态的資料成員以及字面值類型的基類

或者一個字面值類型的數組

上面對于字面值類型的定義還牽涉到平凡類型(trivial type),在另一篇博文會講到。

從定義可以知道,算術類型(整型、浮點型等)、引用、枚舉和指針這些簡單資料類型都屬于字面值類型,此外滿足特定條件的類也屬于字面值類型。

盡管指針和引用都能定義成constexpr,但它們的初始值卻受到嚴格限制。一個constexpr指針的初始值必須是nullptr或者0,或者是存儲于某個固定位址中的對象(如全局變量、靜态變量等)。

constexpr變量

在一個複雜的系統中,很難(幾乎肯定不可能)分辨一個初始值到底是不是常量表達式。盡管我們可以定義一個const變量并把它的初始值設為我們認為的某個常量表達式,但在實際使用時,盡管要求如此,卻常常發現初始值并非常量表達式的情況。是以,對象的定義和使用根本就是兩回事兒。

從C++11開始,規定允許将變量聲明為constexpr類型以便由編譯器來驗證變量的值是否是一個常量表達式。聲明為constexpr的變量一定是一個常量,而且必須用常量表達式初始化。

constexpr int a = 3;//3是常量表達式
constexpr int b = a+1;//b是常量表達式
constexpr int c = get_size();//隻有當get_size()是一個constexpr函數時,才是常量表達式,否則語句錯誤           

constexpr指針

需要注意的是,與const關鍵字不同,一個指針被定義為constexpr,關鍵字僅對指針有效,與指針所指的對象無關:

const int *p = nullptr;//p是一個指向整型常量的指針

constexpr int *q = nullptr;//q是一個指向整型的常量指針,在這一點上類似于int *const p

指針p和q的類型相差甚遠,p是一個指向常量的指針,而q是一個常量指針,其中的關鍵在于constexpr把它所定義的對象置為了頂層const。

與const指針類似,constexpr指針既可以指向常量也可以指向一個非常量。

int i = 3;
constexpr int *p = &i;           

constexpr函數

constexpr函數(constexpr expression)是指能用于常量表達式的函數。定義constexpr函數的方法與其他函數類似,不過要遵循幾項約定(根據2008.6的647号議題)。

1.函數體被聲明為constexpr

2.非虛函數

3.傳回類型及所有形參的類型都必須是字面值類型

4.函數中有且隻有一條return語句(在C++14标準中這條規定被删除)

constexpr int new_sz () {return 24;}
constexpr int foo = new_sz();           

C++11中關于constexpr函數的定義參考連結:cppreference

在C++14中極大放寬了對constexpr函數的定義限制

執行對constexpr函數的初始化時,編譯器把對constexpr函數的調用替換成其結果值。為了能在編譯過程中展開,constexpr函數被隐式地指定為内聯函數。

特别的,constexpr函數允許傳回值并非一個常量:

constexpr size_t scale (size_t cnt) {return new_sz()*cnt}//如果arg是常量表達式,則scale(arg)也是常量表達式

int arr[scale(2)];//正确,scale(2)是常量表達式

constexpr函數不一定傳回常量表達式,傳回值可以為空(return ;)。

和其它函數不一樣,内聯函數和constexpr函數可以在程式中多次定義。畢竟,編譯器要想展開函數僅有函數聲明時不夠的,還需要函數的定義。不過,對于某個給定的内聯函數或者constexpr函數來說,它的多個定義必須完全一緻。基于這個原因,内聯函數和constexpr函數通常定義在頭檔案中。

constexpr構造函數

盡管構造函數不能是const的,但是字面值常量類的構造函數可以是constexpr函數。事實上,一個字面值常量類必須至少提供一個constexpr構造函數。

constexpr構造函數可以聲明成=default的形式(或者是删除函數的形式)。否則,constexpr構造函數就必須既符合構造函數的要求(意味着不能包含傳回語句),又符合constexpr函數的要求(意味着它能擁有的唯一可執行語句就是傳回語句)。綜合這兩點可知,constexpr構造函數體一般來說應該是空的,是以對函數成員的初始化必須放在初始化清單中。

constexpr構造函數必須初始化所有資料成員,constexpr構造函數保證了傳遞給它的所有參數都是constexpr類型的,産生的對象的所有成員也都是constexpr。

本文部分内容摘自《C++ Primer(第5版)》