C++ 之 constexpr和常量表达式

C++之constexpr和常量表达式

1. 常量表达式简介

常量表达式(const expression) 是指不会改变并且在编译过程就能得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的 const 对象也是常量表达式。后面将会提到,C++语言中有几种情况下是要用到常量表达式的。

一个对象(或表达式)是不是常量表达式是由它的数据类型和初始值共同决定,例如:

const int max_files = 20;			// max_files 是常量表达式
const int limit = max_files + 1;	// limit 是常量表达式
int staff_size = 27;				// staff_size 不是常量表达式
const int sz = get_size();			// sz 不是常量表达式

尽管 staff_size 的初始值是一个字面值常量,但由于它的数据类型只是一个普通 int 而非 const int,所以它不属于常量表达式。另一方面,尽管 sz 本身是一个常量,但它的具体值知道运行时才能获取到,所以也不是常量表达式。

2. constexpr 变量

在一个复杂系统中,很难分辨一个初始值到底是不是常量表达式。当然可以定义一个 const 变量并把它的初始值设为我们认为的某个常量表达式,但在实际使用时,尽管要求如此却常常发现初始值并非常量表达式的情况。可以这么说,在此种情况下,对象的定义和使用是两回事。

C++11 新标准中规定,允许将变量声明为 constexpr 类型以便有编译器来验证变量的值是否是一个常量表达式。声明为 constexpr 的变量一定是一个常量,而且必须用常量表达式初始化:

constexpr int mf = 20;			// 20 是常量表达式
constexpr int limit = mf + 1;	// mf + 1 是常量表达式
constexpr int sz = sizes();		// 只有当 size 是一个 constexpr 函数时才是一条正确的声明语句		

尽管不能使用普通函数作为 constexpr 变量的初始值,但是新标准中允许定义一种特殊的 constexpr 函数(在后面会介绍)。这种函数应该是足够简单以使得编译时就可以计算其结果,这样就能用 constexpr 函数去初始化 constexpr 变量了。

note: 一般来说,如果你认定一个变量是一个常量表达式,那就把它声明为 constexpr 类型

3. 字面值类型

常量表达式的值需要在编译时就得到计算,因此对声明constexpr 时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,就把它们称为“字面值类型” (literal type)。

到目前位置接触过的数据类型中,算数类型、引用和指针都属于字面值类型(其中数据成员都是字面值类型的聚合类是字母值常量类)。自定义类、IO库、string类型则不属于字面值类型,也就不能定义成constexpr

尽管指针和引用都能定义成 constexpr,但他们的初始值却收到严格限制。一个constexpr指针的初始值必须是nullptr或者 0 ,或者是存储于某个固定地址中的对象。其中需要注意的是,函数体内定义的变量一般来说并非存放在固定的地址中,因此constexpr指针不能指向这样的变量。相反,定义于所有函数体之外的对象其地址固定不变,能用来初始化 constexpr 指针。

note1:其中数据成员都是字面值类型的聚合类是字母值常量类。如果一个类不是聚合类,但它符合下述要求,则它也是一个字面值常量类:

  • 数据成员必须都是字面值类型。
  • 类必须至少含有一个constexpr构造函数
  • 如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类型,则初始值必须使用成员自己的constexpr构造函数。
  • 类必须使用析构函数的默认定义,该成员负责销毁类的对象。

note2:
尽管构造函数不能是const的,但是字面值常量类的构造函数可以是constexpr函数。事实上,一个字面值常量类必须至少提供一个 constexpr 的构造函数。此时constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造函数,或者是一条常量表达式。其中constexpr构造函数用于生产constexpr对象以及constexpr函数的参数或返回类型:

4. 指针和constexpr

必须明确一点,在constexpr声明中如果定义了一个指针,限定符 constexpr 仅对指针有效,与指针所指的对象无关:

const int *p = nullptr;			// p 是一个指向 整形 常量的指针
constexpr int *q = nullptr;		// q 是一个指向 整数 的常量指针

int k = 99;

void test02() {
	int i = 10, j = 20;
	
	const int *p = &i;			// right,p 是一个指向 整形 常量的指针
	constexpr int *q = nullptr;	// q 是一个指向 整数 的常量指针
    constexpr int *q = &j;		// error: '& j' is not a constant expression
    constexpr int *q = p;		// error: 'p' was not declared 'constexpr'
    constexpr int *q2 = q;		// right
    constexpr int *q3 = &k;		// right, 因为此时k定义在函数体外,为一个常量值
	
	
	p = &i;	// right
	q = &j;	// error: assignment of read-only variable 'q'
	
	cout << *p << endl; // 10
//	cout << *q << endl;
}

其中p和q的类型相差甚远,p是一个指向常量的指针,而q是一个常量指针,其中的关键在于 constexpr 把它定义的对象置位了顶层 const。

与其他常量指针类型,constexpr 指针既可以指向常量也可以指向一个非常亮。

constexpr int *np = nullptr;	// np 是一个指向整数的常量指针,其值为空
int j =0;						
constexpr int i = 42;			// i 的类型是整形常量

// i和j都必须定义在函数体之外
constexpr const int *p = &i;	// p 是常量指针,指向整形常量 i
constexpr int *p1 = &j;			// p1 是常量指针,指向整数 j

5. constexpr函数

constexpr 函数(constexpr function)是指能用于常量表达式的函数。定义 constexpr 函数的方法与其他函数类型,不过要遵循几项约定:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句:

constexpr int new_sz() { return 42; }
constexpr int foo = new_sz();		// right,foo是一个常量表达式

此时 new_sz() 为一个无参的constexpr 函数。因为编译器能在程序编译时验证new_sz函数返回的是常量表达式,所以可以用 new_sz 函数初始化 constexpr 类型的变量foo。

执行该初始化任务时,编译器把对 constexpr 函数的调用替换成其结果值。为了能在编译过程中随时展开,constexpr 函数被隐式地指定为内联函数。

constexpr 函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。例如,constexpr 函数中可以有空语句、类型别名(typedef)以及 using 声明。

C++中允许 constexpr 函数的返回值并非一个常量:

// 如果 arg 是常量表达式,则scale(arg) 也是一个常量表达式
constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }

int arr[scale(2)];		// right, scale(2)是常量表达式
int i = 1;				// i不是常量表达式
int a2[scale(i)];		// scale(i)不是常量表达式
	
cout << sizeof(arr) << endl; // 336 = 2 * 42 * 4
cout << sizeof(a2) << endl;  // 168 = 1 * 42 * 4


cout << typeid(arr).name() << endl; 	// A84_i 
//	cout << typeid(a2).name() << endl;  // error, cannot create type information for type ‘int [(<anonymous> + 1)]’ because it involves types of variable size
cout << typeid(scale(1)).name() << endl; // y
cout << typeid(i).name() << endl; 		// i

当scale 的实参是常量表达式时,它的返回值是常量表达式;反之则不然。

如上例所示,当给scale函数传入的一个字面值2的常量表达式时,它的返回类型也是常量表达式。此时,编译器用相应的结果值替换对scale函数的调用。

如果用一个非常量表达式调用scale函数,比如 int 类型的对象 i ,则返回值是一个非常量表达式。当把 scale 函数用在需要常量表达式的上下文中时,由编译器负责检查函数的结果是否符合要。如果结果恰好不是常量表达式,编译器将会发出错误信息。

参考文献:《c++ primer 第五版》

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++11 引入了 constexpr 关键字,用于指定函数或对象是常量表达式常量表达式是在编译时就能计算出结果的表达式,它可以用于数组大小、模板参数等需要在编译时确定的地方。 constexpr 函数 constexpr 函数是指能在编译时求值的函数,它的返回值可以作为常量表达式使用constexpr 函数的参数和返回值必须是字面类型。 例如,下面的函数就是一个 constexpr 函数: ```cpp constexpr int square(int x) { return x * x; } ``` 我们可以在编译时计算出 square(5) 的值,因此它是一个常量表达式constexpr 对象 constexpr 对象是指在编译时就能计算出值的对象。constexpr 对象必须被声明为 const,而且必须用常量表达式初始化。 例如,下面的语句定义了一个 constexpr 对象: ```cpp constexpr int max_num = 100; ``` 我们可以在编译时就知道 max_num 的值是 100,因此它是一个常量表达式constexpr 函数和常量表达式的限制 constexpr 函数和常量表达式有一些限制: 1. constexpr 函数必须有一个返回值,而且返回值必须是字面类型。 2. constexpr 函数的函数体必须足够简单,能在编译时被求值。 3. constexpr 函数不能包含任何副作用,比如修改全局变量或调用非 constexpr 函数。 4. constexpr 函数的参数和返回值必须是字面类型。 5. constexpr 对象必须被声明为 const,而且必须用常量表达式初始化。 6. constexpr 对象的类型必须是字面类型。 总结 constexpr 关键字用于指定函数或对象是常量表达式constexpr 函数和常量表达式必须在编译时就能计算出值,它们有一些限制。constexpr 函数和常量表达式可以用于数组大小、模板参数等需要在编译时确定的地方。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值