6.1.3 常量表达式值

在代码清单6-3中我们看到了由constexpr关键字修饰的“变量”c和f,这样声明的变量就是所谓的常量表达式值(constant-expression value)。通常情况下,常量表达式值必须被一个常量表达式赋值,而跟常量表达式函数一样,常量表达式值在使用前必须被初始化。

而使用constexpr声明的数据最常被问起的问题是,下列两条语句有什么区别:


const int i=1;

constexpr int j=1;


事实上,两者在大多数情况下是没有区别的。不过有一点是肯定的,就是如果i在全局名字空间中,编译器一定会为i产生数据。而对于j,如果不是有代码显式地使用了它的地址,编译器可以选择不为它生成数据,而仅将其当做编译时期的值(是不是想起了光有名字没有产生数据的枚举值,以及不会产生数据的右值字面常量?事实上,它们也都只是编译时期的常量)。

这里还要提一下浮点常量。有的时候,我们在常量表达式中会看到浮点数。通常情况下,编译器对浮点数做编译时期常量这件事情很敏感。因为编译时环境和运行时环境可能有所不同,那么编译时的浮点常量和实际运行时的浮点数常量可能在精度上存在差别。不过在C++11中,编译时的浮点数常量表达式值还是被允许的。标准要求编译时的浮点常量表达式值的精度要至少等于(或者高于)运行时的浮点数常量的精度。

而对于自定义类型的数据,要使其成为常量表达式值的话,则不像内置类型这么简单。C++11标准中,constexpr关键字是不能用于修饰自定义类型的定义的。比如下面这样的类型定义和使用:


constexpr struct MyType{int i;}

constexpr MyType mt={0};


在C++11中,就是无法通过编译的。正确地做法是,定义自定义常量构造函数(constent-expression constructor)。我们可以看看代码清单6-4所示的例子。

代码清单6-4


struct MyType{

constexpr MyType(int x):i(x){}

int i;

};

constexpr MyType mt={0};

//编译选项:g++ -c-std=c++11 6-1-4.cpp


代码清单6-4中,我们对MyType的构造函数进行了定义。不过在定义前,我们加上了constexpr关键字。通过这样的定义,MyType类型的constexpr的变量mt的定义就可以通过编译了。

常量表达式的构造函数也有使用上的约束,主要的有以下两点:

❑函数体必须为空。

❑初始化列表只能由常量表达式来赋值。

通常第二点跟常量表达式函数一样,是容易出错的,读者在使用中应该注意,形如下面的常量表达式构造函数都是无法通过编译的:


int f();

struct MyType{int i;constexpr MyType():i(f()){}};


当然,这里还需要注意的是,虽然我们声明的是常量表达式构造函数,但是其编译时的“常量性”则体现在类型上,如代码清单6-5所示。

代码清单6-5


include <iostream>

using namespace std;

struct Date{

constexpr Date(int y,int m,int d):

year(y),month(m),day(d){}

constexpr int GetYear(){return year;}

constexpr int GetMonth(){return month;}

constexpr int GetDay(){return day;}

private:

int year;

int month;

int day;

};

constexpr Date PRCfound{1949,10,1};

constexpr int foundmonth=PRCfound.GetMonth();

int main(){cout<<foundmonth<<endl;}//10

//编译选项:g++ -std=c++11-c 6-1-5.cpp


在代码清单6-5中,我们为Date类型声明了常量表达式构造函数,随后定义了constexpr的变量PRCfound。此外,还为Date定义常量表达式的成员函数,可以看到,可以从PRCfound中拿出成员month,赋给一个常量表达式值foundmonth。如果PRCfound的成员变量在这里不具有编译时的常量性,显然是不可能做到的。

这里还需要注意一下常量表达式的成员函数。在C++11中,不允许常量表达式作用于virtual的成员函数。这个原因也是显而易见的,virtual表示的是运行时的行为,与“可以在编译时进行值计算”的constexpr的意义是冲突的。

跟常量表达式函数一样,常量表达式构造函数也可以用于非常量表达式中的类型构造,重写了编译器也会报错的。因而,程序员不必为类型再重写一个非常量表达式版本。