首页 > 其他分享 >编译期计算

编译期计算

时间:2023-07-30 12:32:38浏览次数:30  
标签:10 const 函数 constexpr int value 编译 计算

模板元函数基本概念

支持在编译期调用并完成计算的函数即称为模板元函数,由于是在编译期完成,进而改善运行时的性能。元函数实际上即为C++中的一个模板类。

元函数的通常形式为:

template<typename T, typename Ts>                         // 元函数列表
struct MetaFunction {                                     // 元函数名
    using Type = some-type-define;                        // 返回的类型元数据
    static constexpr int value = some-comile-time-cal     // 返回的数值元数据
};

和函数调用一样,元函数调用也有形参、实参的概念,即template parameter 和template argument;

数值元函数

编译期计算斐波那契数列的第N项

template<size_t N>
struct Fibonacci {
    static const size_t value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};

template<>
struct Fibonacci<0> {
    static const size_t value = 0;
};
static_assert(Fibonacci<10>::value == 55);

其中Fibonacci即为一个数值元函数,在编译期计算值;

类型元函数

类型元函数在模板泛型编程中经常遇到,例如std::remove_reference:

template<class T> struct remove_reference { typedef T type; };
template<class T> struct remove_reference<T&> { typedef T type; };
template<class T> struct remove_reference<T&&> { typedef T type; };

混合元编程

这里混合:可理解为利用模板元函数在编译期生成代码,并在运行期执行生成后的代码;引用【3】中的代码为例子:

template<typename T,std::size_t N>
struct DotProductT
{
    static inline T result(const T* a, const T* b)
    {
        return (*a)*(*b)+DotProductT<T, N-1>::result(a + 1, b + 1);
    }
};
template<typename T>
struct DotProductT<T,0>
{
    static inline T result(const T*, const T*)
    {
        return T{};
    }
};

template<typename T,std::size_t N>
auto DotProductProcess(std::array<T,N> const& x,std::array<T,N> const& y)
{
    return DotProductT<T,N>::result(x.begin(),y.begin());
}
std::array<int, 5> a{1, 2, 3, 4, 5};
    DotProductProcess(a, a);

constexpr

constexpr在c++11引入,并在c++14和c++17中得到增强;经过constexpr声明的对象,和const修饰的对象一样,具有只读属性,并且变量作为编译器常量,但const对象不一定在编译期来初始化;

即所有constexpr对象都是const对象,而并非所有的const对象都是constexpr对象【2】;

1)constexpr修饰的变量【1】

1.its type must be a LiteralType. // 字面类型
2.it must be immediately initialized
3. the full-expression of its initialization, including all implicit conversions, constructors calls, etc, must be a constant expression // 常量表达式

自定义constexpr类型:

struct Dot {
    constexpr Dot(int val) : m_val(val) { };
    constexpr ~Dot() = default;
    constexpr int GetVal() const
    {
        return m_val;
    }
    constexpr int SetVal(int val)
    {
        return m_val = val;
    }
private:
    int m_val;
};

constexpr Dot d(5); // compile OK

int val = 5;
constexpr Dot d(val); // compile error:初始化非常量表达式

int val = 5;
Dot d(val); // compile OK

3)constexpr修饰的静态成员变量,在C++17中自带inline属性,因此不需要再进行类外定义;

4)constexpr修饰的函数

当函数入参为常量时则在编译期完成计算;而入参为运行时变量则退化为普通函数;

constexpr int Add(int a, int b)
{
    return a + b;
}
// 编译期
static_assert(Add(10, 10) == 20); 
constexpr int result = Add(10, 10);

// 运行期
int a = 10;
static_assert(Add(a, a) == 20);

在C++11中,constexpr有较多的约束:

// C++11 constexpr functions use recursion rather than iteration,支持递归但不支持迭代
// in C++11, they must do so from the conditional operator ?:
// C++11 constexpr functions had to put everything in a single return statement

C++14进行了增强改进,constexpr函数内支持for循环迭代和函数临时变量:

constexpr int Add(int a, int b)
{
    int result = 0;
    for (int tmp = a; tmp <= b; tmp++) {
        result += tmp;
    }
    return result;
}

C++17则支持编译器if 判断:

constexpr bool Compare(int a)
{
    if constexpr (sizeof(a) < sizeof(char)) {
        return true;
    }
    return false;
}

C++20开始vector/string可以在constexpr函数中编译期使用;

通过constexpr可以改造Fibonacci数列计算,性能上相比模板元函数更具优势(减少了模板实例化);

constexpr int Fibonacci(int n)
{
    if (n == 0 || n == 1) {
        return n;
    } else {
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
}

注:在【2】中条款15:只要有可能使用constexpr,就使用它

consteval and constinit

consteval

通过constexpr修饰的函数在上一节中介绍既可以在编译期也可以在运行期运行;

consteval为C++20引入,其声明函数或函数模板为立即函数 ,即函数只支持在编译期运行,同时隐式声明为inline;

consteval int Add(int a, int b)
{
    return a + b;
}

// 编译期
static_assert(Add(10, 10) == 20); 
constexpr int result = Add(10, 10);

// 运行期
int a = 10;
Add(a, a);  // 编译失败,consteval修饰的函数不支持运行期,非常量表达式

constinit

constinit用于显式地指定变量的初始化方式为常量表达式 , 生命周期必须为静态生命周期或thread_local 生命周期 ;同时constinit不具有只读属性,constexpr/const具有只读属性;

constinit int a = 10;
void Process()
{
    static constinit int a = 10;
    static constinit int b = Add(10, 10);
    b = 20; // 不具有只读属性
    constinit thread_local int c = 20;
}

枚举值与静态常量

在上文数值元函数中使用const修饰静态常量,此时类内常量在运行时并没有分配内存空间,简化例子为:

struct Test {
    static const size_t value = 10;
};

void Process(const size_t&)
{
}

static_assert(Test::value == 10); // compile:OK
Process(Test::value); // compile error:undefined reference to `Test::value'

Process函数形参为左值引用,因此需要Test::value中的地址,此时则需要内存并且进行类外定义:

const size_t Test::value;

或者添加inline修饰:

struct Test {
    static inline const size_t value = 10;
};

声明为 constexpr 的静态成员变量是隐式的内联变量 ,因此使用constexpr修饰也可以解决;

注:不同编译器现象可能有差别,在cppinsights 测试即使不用inline声明也可以获取左值地址

类内常量也可以修改为枚举:

struct Test {
    enum { value = 10 };
};

void Process(const size_t&)
{
}

static_assert(Test::value == 10);
Process(Test::value);

枚举值不是左值,因此不存在上述所说的地址问题,但是局限于integral types,而constexpr并没有此局限性;

参考资料

【1】https://en.cppreference.com/w/cpp/language/constexpr

【2】Effective ModernC++

【3】C++ templates

标签:10,const,函数,constexpr,int,value,编译,计算
From: https://blog.51cto.com/u_13137973/6899729

相关文章

  • HotSpot编译执行硬编码生成
    目录背景源码指令解析硬编码总结背景在一个技术群里,有一个哥们对着hotspot的源码问了个问题:源码看一下对应的源码://来源:hotspot/src/cpu/x86/vm/assembler_x86.cppvoidAssembler::notl(Registerdst){intencode=prefix_and_encode(dst->encoding());emit_int8(......
  • 计算机语言的发展历史
    1.计算机语言的发展历史计算机编程语言的发展,是随着计算机本身硬件发展而发展的。硬件速度越快、体积越小、成本越低,应用到人类社会的场景就会越多,那么所需要的算法就会越复杂,也就要求计算机编程语言越高级。最初重达几十吨但一秒只能运算5000次的ENIAC(世界上第一台计算机),只能......
  • Vue3.3 编译宏
    Vue3.3新增了一些语法糖和宏,包括泛型组件、defineSlots、defineEmits、defineOptionsdefineProps父子组件传参<template><div><Childname="xiaoman"></Child></div></template><scriptlang='ts'setup>importChildf......
  • 如何在 Python 中计算列表中的唯一值?
    Python提供了各种方法来操作列表,这是最常用的数据结构之一。使用列表时的一项常见任务是计算其中唯一值的出现次数,这在数据分析、处理和筛选任务中通常是必需的。在本文中,我们将探讨四种不同的方法来计算Python列表中的唯一值。在本文中,我们将介绍如何使用集合模块中的集合、字......
  • v831-c-编译环境部署篇
    学了一遍又学回来了,整理整理v831的环境吧头文件这些头文件上面部分是在python里面在编译成可执行文件之前会创造出来的,不用理他下面则是components里面的,需要在.vscode里面设置一下路径这样基本上就可以了,其他的我们不再vscode里面一键操作,就不设置了工具链路径工具链的路......
  • 反编译工具 Fernflower
    反编译.class文件工具Fernflower首先需要下载依赖包 http://the.bytecode.club/fernflower.jar下载后,切换到文件当前目录,直接使用命令 java-jarfernflower.jar目标文件目标路径 进行反编译即可反编译后的文件会生成到指定目录......
  • 01_llvm编译及创建一个module试用llvm
    LLVM源码编译准备好匹配的环境后,我的环境如下:$cat/proc/versionLinuxversion5.4.0-150-generic(buildd@bos03-amd64-012)(gccversion7.5.0(Ubuntu7.5.0-3ubuntu1~18.04))#167~18.04.1-Ubuntu如果环境较老,可checkout到比较老的分支再编,不然容易出现各种软件版本不......
  • 雀魂4 分数计算
    满贯、跳满、役满、倍满 在《雀魂》中,计分是根据麻将的规则和役种来进行的。玩家在游戏中根据和牌时所达成的役种和条件,来计算得分。计分过程如下:役种计分:根据玩家和牌时所满足的役种,为和牌玩家增加相应的番数。每个役种都有对应的番数,番数越高,得分也越高。番数是麻将游......
  • 超聚变和厦门大学助力兴业银行构建智慧金融隐私计算平台,助力信用卡业务精准营销
    兴业银行与超聚变数字技术有限公司、厦门大学携手,发挥产学研用一体化整体优势联合建设,厦门大学提供先进的算法模型及科研能力,超聚变提供产品解决方案及工程能力,兴业银行提供金融实践能力,三方发挥各自领域优势,强化基础研究与深度应用的高效转化,加速隐私计算与金融科技科研成果的商业......
  • ffmpeg 编译安装android和linux
    ffmpeg编译安装android和linux下载:https://github.com/FFmpeg/FFmpeghttps://www.ffmpeg.org/download.htmlenvirenmentndk:https://github.com/android/ndk/wiki/Unsupported-Downloadssudoapt-getinstallbuild-essentialpkg-configsudoapt-getintalllibx264-dev......