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. 经常使用的Sublime Text 快捷键

    最常用的 Sublime快捷键:

  2. 【Docker】安装docker18.09.6后,无法启动

    ------------------------------------------------------------------------------------------------- | ...

  3. 【Linux】大于2T的磁盘怎么分区?

    环境CentOS7.1 2.9t磁盘 fdisk 只能分区小于2t的磁盘,大于2t的话,就要用到parted 1,将磁盘上原有的分区删除掉: 进入:#parted   /dev/sdb 查看:(par ...

  4. mysql—make_set函数

    使用格式:MAKE_SET(bits,str1,str2,-) 1 返回一个设定值(含子字符串分隔字符串","字符),在设置位的相应位的字符串.str1对应于位0,str2到第1位 ...

  5. .NET Core部署到linux(CentOS)最全解决方案,进阶篇(Supervisor+Nginx)

    在.NET Core部署到linux(CentOS)最全解决方案,常规篇一文,我们详细讲解了传统的.NET Core部署到Linux服务器的方法,学到了Linux在虚拟机下的安装.Xshell,Xft ...

  6. apijson简单使用

    apijson简单使用 介绍 APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库.为简单的增删改查.复杂的查询.简单的事务操作 提供了完全自动化的 ...

  7. Azure Terraform(六)Common Module

    一,引言 之前我们在使用 Terraform 构筑一下 Azure 云资源的时候,直接将所以需要创建的资源全面写在 main.tf 这个文件中,这样写主要是为了演示使用,但是在实际的 Terrafor ...

  8. 获取html中某些标签的值

    一.获取单选按钮radio的值 <!doctype html> <html lang="en"> <head> <meta charset ...

  9. JMETER-正则表达式提取与查看变量是否提取正确

    一.应用场景说明: 在一个线程组中,B请求需要使用A请求返回的数据,也就是常说的关联,将上一个请求的响应结果作为下一个请求的参数,则需要对A请求的响应报文使用后置处理器,其中最方便最常用的就是正则表达 ...

  10. day132:2RenMJ:MJ需求文档&MJ游戏流程&Egret白鹭引擎安装&TypeScript简要介绍

    目录 1.麻将产品需求文档 2.麻将游戏流程 3.Egret白鹭引擎 4.TypeScript简要了解 5.TypeScript快速入门 1.麻将产品需求文档 1.麻将术语 1.名词术语 牌⼦: 序数 ...