constexpr 是什么?

关键字 constexpr (constant expression) 是在 C++11 中引入的,并且在 C++14 中进行了优化。

constexprconst 一样可以用来修饰变量:试图修改 constexpr 变量时,编译器将会报错。

不同于 constconstexpr 还可以修饰函数和类的构造函数。 constexpr 表示值或者返回值是常量,并且如果可能,在编译时计算它们。

一个 constexpr 整型值能够用在任何 const 整型值可以用的地方,例如模板参数和数组的申明。

当值在编译时计算而不是运行时计算时,它能够使程序运行得更快,并使用更少的内存。

为了限制编译时常量计算的复杂性,以及其对编译时间潜在的影响, C++14 标准需要 constexpr 类型必须为字面值类型。

语法

constexpr literal-type identifier = constant-expression ;

constexpr literal-type identifier { constant-expression } ;

constexpr literal-type identifier ( params ) ;

constexpr ctor ( params ) ;

参数

params

一个或者多个参数, 每个参数必须是字面值类型并且本身是 常量表达式

返回值

一个 constexpr 变量或者函数必须返回字面值类型。

constexpr 变量

constexprconst 的主要区别是 const 变量的初始化可以被延时到运行时,而 constexpr 变量必须在编译时初始化。所有的 constexpr 变量都是 const

  • 当变量由字面值类型初始化时,能够声明为 constexpr。如果初始化时由构造函数执行的,那么该构造函数也必须声明为 constexpr

  • 引用类型可以声明为 constexpr 只要满足:引用对象是由 常量表达式初始化的,并且初始化时任何隐式转换也是常量表达式。

  • 所有 constexpr 变量或者函数的声明必须拥有 constexpr 说明符。

举例:

constexpr float x = 42.0;
constexpr float y{108};
constexpr float z = exp(5, 3);
constexpr int i; // Error! Not initialized
int j = 0;
constexpr int k = j + 1; //Error! j not a constant expression

constexpr 函数

constexpr 函数的返回值是在编译时计算的。调用代码需要返回值在编译时初始化一个 constexpr 变量, 或者提供一个非类型模板参数。 当参数是 constexpr 值时,constexpr 函数产生一个编译时的常量。当被调用时传入非 constexpr 参数,或者其返回值在非编译时请求,constexpr 函数和普通函数一样将产生提个运行时的值。(这种行为能够让你避免编写两个相同功能的函数,一个为 constexpr 版本,一个为非 constexpr 版本)

constexpr 函数或者构造函数默认是 inline 的.

以下规则适用于 constexpr 函数:

  • 必须接受并返回字面值类型。
  • 可以是递归的.
  • 不能是虚构的。当类有虚基类时,构造函数不能定义为 constexpr
  • 函数体可以定义为 = default 或者 = delete
  • 函数体不能包含 goto 语句或者 try 块。
  • 显式特化/具体化(explicit specialization)的非 constexpr 模板能够声明为 constexpr
  • 显式特化/具体化(explicit specialization)的非 constexpr 模板不必声明为 constexpr

以下 constexpr 函数规则适用于 Visual Studio 2017 及以后的版本:

  • 可以包含 ifswitch 语句,以及所有循环语句包括 forwhiledo-while
  • 可以包含初始化的局部变量,并且必须是字面值类型,不能是 static 或者 thread-local。该局部变量不必是 const 的。
  • staticconstexpr 成员函数不必隐式为 const
constexpr float exp(float x, int n)
{
return n == 0 ? 1 :
n % 2 == 0 ? exp(x * x, n / 2) :
exp(x * x, (n - 1) / 2) * x;
}

提示:

在 Visual Studio 调试器中, 当调试一个非优化的调试版本,你能够通过在函数内部设置断点来区分 constexpr 函数是否在编译时计算。如果断点能够触发,则为运行时计算,否则,为编译时计算。

示例

以下例子演示了 constexpr 变量, 函数, 以及用户自定义类型。 在 main() 最后, constexpr 成员函数 GetValue() 是在运行时调用的,因为其返回值没有被要求在编译时确定。

#include <iostream>

using namespace std;

// Pass by value
constexpr float exp(float x, int n)
{
return n == 0 ? 1 :
n % 2 == 0 ? exp(x * x, n / 2) :
exp(x * x, (n - 1) / 2) * x;
} // Pass by reference
constexpr float exp2(const float& x, const int& n)
{
return n == 0 ? 1 :
n % 2 == 0 ? exp2(x * x, n / 2) :
exp2(x * x, (n - 1) / 2) * x;
} // Compile-time computation of array length
template<typename T, int N>
constexpr int length(const T(&)[N])
{
return N;
} // Recursive constexpr function
constexpr int fac(int n)
{
return n == 1 ? 1 : n * fac(n - 1);
} // User-defined type
class Foo
{
public:
constexpr explicit Foo(int i) : _i(i) {}
constexpr int GetValue() const
{
return _i;
}
private:
int _i;
}; int main()
{
// foo is const:
constexpr Foo foo(5);
// foo = Foo(6); //Error! // Compile time:
constexpr float x = exp(5, 3);
constexpr float y { exp(2, 5) };
constexpr int val = foo.GetValue();
constexpr int f5 = fac(5);
const int nums[] { 1, 2, 3, 4 };
const int nums2[length(nums) * 2] { 1, 2, 3, 4, 5, 6, 7, 8 }; // Run time:
cout << "The value of foo is " << foo.GetValue() << endl;
}

constexpr 和 const 的异同

相同处:

constexprconst 一样可以用来修饰变量:试图修改 constexpr 变量时,编译器将会报警。一个 constexpr 整型值能够用在任何 const 整型值可以用的地方,例如模板参数和数组的申明。

不同处:

constexpr 还可以修饰函数和类的构造函数。
constexpr** 表示值或者返回值是常量,并且如果可能,在编译时计算它们。

当一个值在编译时计算而不是运行时计算时,它能够使程序运行得更快,并使用更少的内存。

所有的 constexpr 对象都是 const 的,但不是所有的 const 对象都是 constexpr 的。

示例

int sz;                             // non-constexpr variable
… constexpr auto arraySize1 = sz; // error! sz's value not
// known at compilation std::array<int, sz> data1; // error! same problem constexpr auto arraySize2 = 10; // fine, 10 is a
//compile-time constant std::array<int, arraySize2> data2; // fine, arraySize2
// is constexpr
int sz;                             // as before
… const auto arraySize = sz; // fine, arraySize is
// const copy of sz std::array<int, arraySize> data; // error! arraySize's value
// not known at compilation

应该使用 constexpr 的场景

只要允许,尽可能使用 constexpr,当值在编译时计算而不是运行时计算时,它能够使程序运行得更快,并使用更少的内存。

不应该使用 constexpr 的场景

constexpr 是对象或者函数接口的一部分,所以如果你使用了 constexpr 但反悔了,移除 constexpr 可能会导致大量的调用代码编译失败。(比如添加 I/O 操作用于调试或者性能调优可能导致这样的问题,因为 I/O 语句通常不是在 constexpr 函数中执行的。)

引用

constexpr 的来龙去脉的更多相关文章

  1. C++11特性——变量部分(using类型别名、constexpr常量表达式、auto类型推断、nullptr空指针等)

    #include <iostream> using namespace std; int main() { using cullptr = const unsigned long long ...

  2. [c++] constexpr and literal class

    稀奇古怪的新特性,菜鸟在此啄上一啄. 1. When should literal classes be used in C++?   2. int i; // not constant const ...

  3. 【转载】VC维的来龙去脉

    本文转载自 火光摇曳 原文链接:VC维的来龙去脉 目录: 说说历史 Hoeffding不等式 Connection to Learning 学习可行的两个核心条件 Effective Number o ...

  4. Sql Server来龙去脉系列 必须知道的权限控制核心篇

    最近写了<Sql Server来龙去脉系列  必须知道的权限控制基础篇>,感觉反响比较大.这可能也说明了很多程序猿对数据库权限控制方面比较感兴趣,或者某些技术点了解的没有很透彻. 有些人看 ...

  5. Sql Server来龙去脉系列 必须知道的权限控制基础篇

    题外话:最近看到各种吐槽.NET怎么落寞..NET怎么不行了..NET工资低的帖子.我也吐槽一句:一个程序猿的自身价值不是由他选择了哪一门技术来决定,而是由他自身能创造出什么价值来决定. 在进入本篇内 ...

  6. Sql Server来龙去脉系列之四 数据库和文件

        在讨论数据库之前我们先要明白一个问题:什么是数据库?     数据库是若干对象的集合,这些对象用来控制和维护数据.一个经典的数据库实例仅仅包含少量的数据库,但用户一般也不会在一个实例上创建太多 ...

  7. Sql Server来龙去脉系列之三 查询过程跟踪

    我们在读写数据库文件时,当文件被读.写或者出现错误时,这些过程活动都会触发一些运行时事件.从一个用户角度来看,有些时候会关注这些事件,特别是我们调试.审核.服务维护.例如,当数据库错误出现.列数据被更 ...

  8. Sql Server来龙去脉系列之二 框架和配置

    本节主要讲维持数据的元数据,以及数据库框架结构.内存管理.系统配置等.这些技术点在我们使用数据库时很少接触到,但如果要深入学习Sql Server这一章节也是不得不看.本人能力有限不能把所有核心的知识 ...

  9. Sql Server来龙去脉系列之一 目录篇

    从工作一直到现在都没怎么花功夫深入学习下Sql Server数据库,在使用Sql Server时90%的时间基本上都是在接触T-SQL,所以数据库这块基本上属于菜鸟级别.至于数据库的底层框架以及运行机 ...

随机推荐

  1. 【数据库】MySQL & SQL 介绍

    文章目录 MySQL & SQL 介绍 1.MySQL的背景 2.MySQL的优点 3.MySQL的安装 4.MySQL服务的启动和停止 方式一 方式二 5.MySQL服务的登录和退出 方式一 ...

  2. 【ORACLE】11g rac+dg

    首先感谢群友分享的文档,在这里先感谢哆啦B梦,非常感谢 该文档主要指导如何利用现有的RAC环境搭建一套RAC与单实例的DG的环境  ============================主机配置信息 ...

  3. P2979 [USACO10JAN]奶酪塔Cheese Towers(完全背包,递推)

    题目描述 Farmer John wants to save some blocks of his cows' delicious Wisconsin cheese varieties in his ...

  4. SDUST数据结构 - chap9 排序

    判断题: 选择题: 编程题: 7-1 排序: 输入样例: 11 4 981 10 -17 0 -20 29 50 8 43 -5 输出样例: -20 -17 -5 0 4 8 10 29 43 50 ...

  5. [Usaco2007 Jan]Telephone Lines架设电话线

    题目描述 FarmerJohn打算将电话线引到自己的农场,但电信公司并不打算为他提供免费服务.于是,FJ必须为此向电信公司支付一定的费用.FJ的农场周围分布着N(1<=N<=1,000)根 ...

  6. USB限流IC,输入5V,输出5V,最大3A限流

    USB限流芯片,5V输入,输出5V电压,限流值可以通过外围电阻进行调节,PWCHIP产品中可在限流范围0.4A-4.8A,并具有过压关闭保护功能. 过压关闭保护: 如芯片:PW1555,USB我们一半 ...

  7. linux串口编程

    按照对linux系统的理解,串口编程的顺序无非就是open,read,write,close,而串口有波特率.数据位等重要参数需要设置,因此还应该用到设置函数,那么接下来就带着这几个问题去学习linu ...

  8. (013)每日SQL学习:日期的各种计算

    1.确定两个日期之间的工作日天数 --确定两个日期之间的工作日天数with x0 as (select to_date('2018-01-01','yyyy-mm-dd') as 日期 from du ...

  9. 【PC Basic】CPU、核、多线程的那些事儿

    一.CPU与核的概念 1.半导体中名词[Wafer][Chip][Die]中文名字和用途 Wafer--晶圆 wafer 即为图片所示的晶圆,由纯硅(Si)构成.一般分为6英寸.8英寸.12英寸规格不 ...

  10. promise有几种状态,什么时候会进入catch

    三个状态:pending.fulfilled.reject两个过程:padding -> fulfilled.padding -> rejected当pending为rejectd时,会进 ...