C++11 constexpr

【1】constexpr VS const

const修饰的都是具有运行时常量性;

constexpr修饰的都是具有编译时常量性;

假如你将一个成员函数标记为constexpr,则顺带也将它标记为了const。如果你将一个变量标记为constexpr,则同样它是const的。

但相反并不成立,一个const的变量或函数,并不是constexpr的。

【2】constexpr值

(1)内置类型

1.1 非浮点常量

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

const int i = 2020;
constexpr int j = 2020;

实际上,两者在大多数情况下是没有区别的。唯一的区别:

如果i在全局名字空间中,编译器一定会为i产生数据。

对于j,如果没有代码显式地使用它的地址,编译器就可以选择不为它生成数据,而仅将其当做编译期的值。

1.2 浮点常量

在C++11中,编译时的浮点数常量表达式值还是被允许的。

但是标准要求编译时的浮点常量表达式值的精度要至少等于(或高于)运行时的浮点数常量的精度。

(2)自定义类型

C++11标准中,constexpr关键字“默认”是不能用于修饰自定义类型的值。

对于自定义类型的数据,要想成为常量表达式值的话,需要定义自定义常量构造函数。如下示例:

#include <iostream>
using namespace std;

class Circle
{
public:
    constexpr Circle(int x, int y, int radius) : m_x(x), m_y(y), m_radius(radius)
    { }
    constexpr double getArea() const
    {
        return m_radius * m_radius * 3.1415926;
    }
private:
    int m_x;
    int m_y;
    int m_radius;
};

int main()
{
    constexpr Circle objC(0, 0, 10);
    constexpr double area = objC.getArea();
}

常量表达式的构造函数需要注意两点(尤其特别注意第二点):

[1] 函数体必须为空。

[2] 初始化列表只能由常量表达式进行赋值。

上例中,也定义了一个常量表达式的成员函数getArea。

关于常量表达式的成员函数,在C++11标准中不允许常量表达式作用于virtual的成员函数。

原因显而易见,virtual表示的时运行时的行为,与constexpr编译期的意义是冲突的。

另外,常量表达式构造函数也可以用于非常量表达式中的对象构造,重写了编译器会报错。

【3】constexpr函数

为了使函数获取编译期的计算能力,你必须指定constexpr关键字到这个函数。如下示例:

constexpr int multiply(int x, int y)
{
    return x * y;
}

// 编译期计算
const int val = multiply(10, 10);

除了编译时计算的性能优化。constexpr的另外一个优势是,它允许函数被应用在以前调用宏的所有场合。

例如,你想要一个计算数组size的函数,size是10的倍数。

如果不用constexpr,你需要创建一个宏或者使用模板,因为你不能用函数的返回值去声明数组的大小。

但是若用constexpr,你就可以调用一个constexpr函数去声明一个数组。如下示例:

constexpr int getDefaultArraySize(int multiplier)
{
    return 10 * multiplier;
}

int myArray[getDefaultArraySize(2)];

关于constexpr函数的限制,一个constexpr函数有必须遵循的要求:

(1)函数中只能有一个return语句

(2)只能调用其它constexpr函数

(3)只能使用全局constexpr变量

注意递归并不受限制。但只允许一个返回语句,那如何实现递归呢?可以使用三元运算符(?:)。如下计算n的阶乘:

constexpr int factorial(int n)
{
    return n > 0 ? n * factorial(n - 1) : 1;
}

现在你可以使用factorial(2),编译器将在编译时计算这个值,这种方式运行更巧妙的计算,与内联截然不同。你无法内联一个递归函数。

除此而外,constexpr函数还有那些特点呢?

(1)一个constexpr函数,只允许包含一行可执行代码。

但允许包含typedefs、 using declaration && directives、静态断言等。

(2)一个声明为constexpr的函数同样可以在运行时被调用,当这个函数的参数是非常量的。

这意味着你不需要分别写运行时和编译时的函数。如上阶乘的应用示例:

1 int m;
2 std::cin >> m;
3 constexpr int result = factorial(m);

(3)关于编译期使用对象,第二节constexpr值内容(Circle类型的objC对象)已介绍。

【4】constexpr其他应用

常量表达式也是可以用于模板函数的。

但由于模板中类型的不确定性,模板函数是否会被实例化为一个能够满足编译时常量性的版本通常是未知的。

针对此问题,C++11标准规定:

当声明为常量表达式的模板函数后,而某个模板函数的实例化结果不满足常量表达式的需求的话,constexpr会被自动忽略。

应用示例如下:

#include <iostream>
using namespace std;

template <typename T> 
constexpr T constExp(T t)
{
    return t;
}

struct NotLiteral
{
    NotLiteral() { i = 10; }
    int i;
};

int main()
{
    NotLiteral n1;
    NotLiteral n2 = constExp(n1);
20//   constexpr NotLiteral n3 = constExp(n1);   // 无法通过编译
    constexpr int a = constExp(1);
}

结构体NotLiteral不是一个定义了常量表达式构造函数的类型,因此不能声明为常量表达式的值。

而模板函数constExp一旦以NotLiteral为参数的话,那么其constexpr关键字将被忽略。

good good study, day day up.

顺序 选择 循环 总结