C++之constexpr 编译时计算

C++新特性整理目录:

那么现在让我们带着这样几个问题,来学习一下constexpr吧:为什么要使用constexpr?使用constexpr能给我们的程序带来什么好处?能够解决在以往编程过程中的什么问题?

概述

constexpr是C++从11标准开始引入的核心特性,它代表"常量表达式",旨在将计算从运行时转移到编译时,实现真正的零开销抽象。其核心思想是:允许编译器在编译期间执行函数和对象初始化,从而消除运行时开销,同时增强代码安全性。

演进历程展现了C++对编译时计算能力的持续强化。C++11中,constexpr函数只能包含单条return语句,主要用于基础数学计算。C++14解除了函数体限制,允许循环、分支和局部变量,使复杂算法得以编译时执行。C++17引入if constexpr实现编译时分支,Lambda默认支持constexpr。C++20实现了质的飞跃:允许动态内存分配、标准容器(vector/string)和虚函数在编译时使用,几乎实现了"编译时执行任何运行时能执行的代码"。

核心价值体现在三个层面:性能上,将已知数据的计算提前到编译时,消除运行时计算开销;安全上,通过static_assert在编译时验证业务逻辑,提前发现错误;表达力上,用直观的普通C++语法替代晦涩的模板元编程。在嵌入式系统、游戏开发、高性能计算等领域,constexpr使得配置解析、数学运算、数据结构初始化等操作能够在编译时完成,既保证了性能又增强了安全性。

本质上,constexpr让C++程序员能够用同一套代码表达编译时和运行时逻辑,编译器根据上下文自动选择最佳执行时机,这是对"零开销抽象"哲学的最好实践。

一、constexpr 在 C++ 各版本中的发展
C++11:初代引入

解决的问题:

  • 编译时常量表达式的严格定义:明确区分编译时可计算和运行时计算
  • 替代部分模板元编程:简化编译时计算
  • 数组大小和模板参数的动态计算:允许使用函数结果作为编译时常量

核心限制:

  • 函数体只能包含单个 return 语句
  • 不能有局部变量、循环、分支等
  • 所有参数必须是字面量类型
  • 返回类型必须是字面量类型
    使用示例:
cpp 复制代码
// 简单函数(只有一条return语句)
constexpr int square(int x) {
    return x * x;  // 只能有一条语句
}

constexpr int factorial(int n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);  // 递归,但不能有if语句
}

// 模板参数使用
template<int N>
struct Array {
    int data[N];
};
int main()
{
	
    // 基础变量声明
    constexpr int size = 10;
    constexpr double pi = 3.141592653589793;

    // 编译时计算
    constexpr int arr_size = square(5);  // 25
    int arr25[arr_size];  // 数组大小 = 25

    // 类型定义
    struct Point {
        constexpr Point(double x, double y) : x(x), y(y) {}
        double x, y;
    };

    constexpr Point origin(0.0, 0.0);
    constexpr Point unit(1.0, 0.0);

    Array<factorial(4)> arr24;  // Array<24>
}

局限性示例:

cpp 复制代码
// 以下代码在C++11中无效:
constexpr int sum_to_n(int n) {
    int result = 0;           // 错误:局部变量
    for (int i = 1; i <= n; ++i) {  // 错误:循环
        result += i;
    }
    return result;
}
C++14:重大放松

解决的问题:

  • 函数体限制过多:允许更自然的函数写法
  • 编译时计算能力有限:支持更复杂的算法
  • 实用性不足:难以编写实际有用的constexpr函数

核心改进:

  • 允许局部变量(非static、非thread_local
  • 允许ifswitchforwhile等控制流
  • 允许修改函数内的局部变量
  • 允许void返回类型
    使用示例:
cpp 复制代码
// 复杂的编译时函数
constexpr int fibonacci(int n) {
    if (n <= 1) return n;

    int a = 0, b = 1;  // C++14允许局部变量
    for (int i = 2; i <= n; ++i) {  // C++14允许循环
        int temp = a + b;
        a = b;
        b = temp;
    }
    return b;
}
// 编译时字符串处理
constexpr int string_length(const char* str) {
    int length = 0;
    while (str[length] != '\0') {
        ++length;
    }
    return length;
}

// 编译时查找
constexpr int find(const int* arr, int size, int target) {
    for (int i = 0; i < size; ++i) {
        if (arr[i] == target) {
            return i;
        }
    }
    return -1;
}

// 更复杂的数学计算
constexpr double power(double base, int exp) {
    if (exp == 0) return 1.0;

    double result = 1.0;
    int abs_exp = exp > 0 ? exp : -exp;

    for (int i = 0; i < abs_exp; ++i) {
        result *= base;
    }

    return exp > 0 ? result : 1.0 / result;
}

int main()
{
    constexpr int fib10 = fibonacci(10);  // 55
    constexpr auto len = string_length("hello");  // 5
    constexpr int arr[] = { 1, 2, 3, 4, 5 };
    constexpr int pos = find(arr, 5, 3);  // 2
}
C++17:编译时编程的飞跃

解决的问题:

  • 编译时分支选择不够优雅 :提供if constexpr
  • Lambda表达式限制:支持constexpr lambda
  • 编译时静态多态:简化模板元编程

核心改进:

  • if constexpr:编译时条件分支
  • Lambda表达式默认constexpr
  • constexpr静态成员变量不再需要类外定义
  • 更多标准库组件支持constexpr
    使用示例:
cpp 复制代码
// if constexpr:编译时分支
template<typename T>
constexpr auto get_value(T t) {
    if constexpr (std::is_pointer_v<T>) {
        return *t;  // 编译时决定是否生成这段代码
    }
    else if constexpr (std::is_integral_v<T>) {
        return t + 1;
    }
    else {
        return t;
    }
}

// 编译时类型分发
template<typename... Ts>
constexpr auto sum_all(Ts... args) {
    if constexpr (sizeof...(args) == 0) {
        return 0;
    }
    else {
        return (args + ...);  // 折叠表达式
    }
}

// constexpr lambda(C++17起)
constexpr auto square_lambda = [](int x) constexpr {
    return x * x;
    };

// 编译时lambda调用
constexpr int result = [](int n) constexpr {
    int sum = 0;
    for (int i = 1; i <= n; ++i) sum += i;
    return sum;
    }(100);  // 编译时计算1到100的和

// 编译时字符串处理增强
constexpr bool starts_with(std::string_view str, std::string_view prefix) {
    if (str.length() < prefix.length()) return false;
    for (size_t i = 0; i < prefix.length(); ++i) {
        if (str[i] != prefix[i]) return false;
    }
    return true;
}

// 编译时验证
template<size_t N>
struct FixedString {
    constexpr FixedString(const char(&str)[N]) {
        for (size_t i = 0; i < N; ++i) {
            data[i] = str[i];
        }
    }
    char data[N];
};

// 使用if constexpr实现编译时算法选择
template<typename Iterator>
constexpr void sort(Iterator begin, Iterator end) {
    using value_type = typename std::iterator_traits<Iterator>::value_type;

    if constexpr (std::is_integral_v<value_type>) {
        // 对整数使用快速排序(编译时选择)
        quick_sort(begin, end);
    }
    else {
        // 其他类型使用插入排序
        insertion_sort(begin, end);
    }
}

// 静态成员变量简化
struct MathConstants {
    static constexpr double pi = 3.141592653589793;
    static constexpr double e = 2.718281828459045;
    // C++17:不需要类外定义
};

// 编译时数组操作
constexpr auto create_lookup_table() {
    std::array<int, 10> table = {};  //  C++14std::array构造函数还不是constexpr,C++17支持了
    for (int i = 0; i < 10; ++i) {
        table[i] = i * i;  // C++14允许修改局部变量
    }
    return table;
}

int main()
{
    constexpr auto val1 = get_value(42);      // 43
    constexpr auto val2 = get_value(&val1);   // 42
    constexpr auto total = sum_all(1, 2, 3, 4);  // 10

    static_assert(starts_with("hello world", "hello"));
    static_assert(square_lambda(5) == 25);

    constexpr auto squares = create_lookup_table();
    static_assert(squares[3] == 9, "3^2 should be 9");
}
C++20:编译时编程的全面能力

解决的问题:

  • 动态内存分配不能用于编译时 :允许new/delete在constexpr中
  • 异常处理限制 :允许try-catch在constexpr中

核心改进

  • 允许newdelete(编译时分配,编译时释放)
  • 允许try-catch(但catch块不能执行)
  • 允许类型转换(dynamic_casttypeid
  • 允许asm声明(但不能执行)
    使用示例:
    (1)动态内存分配(编译时 new/delete)
cpp 复制代码
//编译时动态数组
constexpr auto create_dynamic_array(int size) {
    // C++20允许编译时使用new和delete
    int* arr = new int[size];

    // 初始化数组
    for (int i = 0; i < size; ++i) {
        arr[i] = i * i;  // 平方数
    }

    // 计算总和
    int sum = 0;
    for (int i = 0; i < size; ++i) {
        sum += arr[i];
    }

    // 编译时必须释放内存!
    delete[] arr;
    return sum;
}

// 编译时内存管理规则
constexpr auto memory_demo() {
    // 可以分配,但必须在同一constexpr上下文中释放
    int* p1 = new int(42);
    int* p2 = new int[5] {1, 2, 3, 4, 5};

    // 编译时修改动态内存
    p2[0] = 100;

    int result = *p1 + p2[0];

    // 必须成对释放
    delete p1;
    delete[] p2;

    return result;
}


int main()
{
    constexpr int dynamic_sum = create_dynamic_array(10);
    static_assert(dynamic_sum == 285);  // 0²+1²+...+9² = 285

    constexpr int mem_result = memory_demo();  // 142
}

如果我们返回new的指针会怎么样?示例代码如下:

cpp 复制代码
constexpr auto memory_demo() {
    int* p2 = new int[5] {1, 2, 3, 4, 5};
    return p2;
}


int main()
{
    constexpr int mem_result = memory_demo();  // 142
}

编译试试:

这说明:可以分配,但必须在同一constexpr上下文中释放
(2)异常处理支持【编译时try-catch】

cpp 复制代码
// 编译时try-catch
constexpr int safe_divide(int a, int b) {
    try {
        if (b == 0) {
            throw std::runtime_error("Division by zero");
        }
        return a / b;
    }
    catch (const std::exception& e) {
        // 注意:catch块在编译时执行时必须是常量表达式
        // 但我们可以返回一个默认值
        return 0;
    }
}



// 更复杂的异常处理
constexpr auto parse_number(std::string_view str) -> std::optional<int> {
    try {
        // 尝试解析字符串为整数
        int value = 0;
        for (char c : str) {
            if (c < '0' || c > '9') {
                throw std::invalid_argument("Not a number");
            }
            value = value * 10 + (c - '0');
        }
        return value;
    }
    catch (...) {
        return std::nullopt;
    }
}
constexpr auto memory_demo() {
    int* p2 = new int[5] {1, 2, 3, 4, 5};
    return p2;
}


int main()
{
    constexpr int result1 = safe_divide(10, 2);  // 5
    constexpr int result2 = safe_divide(10, 0);  // 0(编译时捕获异常)

    constexpr auto num1 = parse_number("123");
    constexpr auto num2 = parse_number("12a");  // 返回nullopt
    static_assert(num1.value() == 123);
    static_assert(num2.has_value());
    static_assert(num1.has_value());
}

针对抛出异常的情况,编译器将无法编译通过

(3)编译时多态

说到多态我们就会想到虚函数,那么C++20是通过编译时虚函数调用来支持编译时多态的吗?我们先来看看这样一段代码:

cpp 复制代码
struct Shape {
    constexpr virtual double area() const = 0;
    constexpr virtual std::string_view name() const = 0;
    constexpr virtual ~Shape() = default;  // 虚析构函数也可以constexpr
};

struct Circle : Shape {
    constexpr Circle(double r) : radius(r) {}

    constexpr double area() const override {
        return 3.141592653589793 * radius * radius;
    }

    constexpr std::string_view name() const override {
        return "Circle";
    }

    double radius;
};

struct Rectangle : Shape {
    constexpr Rectangle(double w, double h) : width(w), height(h) {}

    constexpr double area() const override {
        return width * height;
    }

    constexpr std::string_view name() const override {
        return "Rectangle";
    }

    double width, height;
};

constexpr double total_area(const Shape* const* shapes, size_t count) {
    double total = 0.0;
    for (size_t i = 0; i < count; ++i) {
        total += shapes[i]->area();  // 编译时虚函数调用!
    }
    return total;
}



int main()
{
    // 使用示例
    constexpr Circle circle(5.0);
    constexpr Rectangle rect(4.0, 6.0);
    constexpr const Shape* shapes[] = { &circle, &rect };

    constexpr double total = total_area(shapes, 2);
    static_assert(total > 78.0 && total < 114.0);  // 圆面积≈78.54 + 矩形面积24
}

很遗憾,编译无法通过,问题出在这样一段代码:

constexpr const Shape* shapes[] = { &circle, &rect };

这可能是取地址问题,我们对代码做如下修改,看看会怎样?

cpp 复制代码
...
constexpr double area()
{
    Circle *circle = new Circle(5.0);
    Rectangle* rect = new Rectangle(4.0, 6.0);
    double total = total_area(shapes, 2);
    delete circle;
    delete rect;
    return total;
}

int main()
{
    constexpr double total = area();
    static_assert(total > 78.0 && total < 114.0);  // 圆面积≈78.54 + 矩形面积24
}

还是无法编译通过,这次问题在double total = total_area(shapes, 2);我们在改改area()函数看看

cpp 复制代码
constexpr double area()
{
    Circle *circle = new Circle(5.0);
    Rectangle* rect = new Rectangle(4.0, 6.0);
    double total = circle->area()+ rect->area();
    delete circle;
    delete rect;
    return total;
}

依旧无法编译通过,我们将area改成普通成员函数呢?

cpp 复制代码
struct Shape {
    //constexpr virtual double area() const = 0;
    constexpr virtual std::string_view name() const = 0;
    constexpr virtual ~Shape() = default;  // 虚析构函数也可以constexpr
};

struct Circle : Shape {
    constexpr Circle(double r) : radius(r) {}

    constexpr double area() const  {
        return 3.141592653589793 * radius * radius;
    }

    constexpr std::string_view name() const override {
        return "Circle";
    }

    double radius;
};

struct Rectangle : Shape {
    constexpr Rectangle(double w, double h) : width(w), height(h) {}

    constexpr double area() const  {
        return width * height;
    }

    constexpr std::string_view name() const override {
        return "Rectangle";
    }

    double width, height;
};

constexpr double area()
{
    Circle *circle = new Circle(5.0);
    Rectangle* rect = new Rectangle(4.0, 6.0);
    double total = circle->area()+ rect->area();
    delete circle;
    delete rect;
    return total;
}

int main()
{
    constexpr double total = area();
    //static_assert(total > 78.0 && total < 114.0);  // 圆面积≈78.54 + 矩形面积24
}

没有问题,这说明还是编译时虚函数调用问题。那么如何实现编译时多态呢?

我们可以这样理解:
编译期多态是C++中一种在编译阶段实现多态性的技术,主要通过模板和函数重载来实现。与运行期多态不同,编译期多态不依赖虚函数表,而是通过模板实例化和编译器的静态分析来确定调用的具体函数。这种多态性也被称为静态多态。
我们继续看看下面这样一段代码:

cpp 复制代码
#include <iostream>
using namespace std;

class Dog {
public:
	void shout() { cout << "汪汪!" << endl; }
};

class Cat {
public:
	void shout() { cout << "喵喵~" << endl; }
};

template <typename T>
void animalShout(T& animal) {
	animal.shout();
}

int main() {
	Dog dog;
	Cat cat;

	animalShout(dog); // 输出: 汪汪!
	animalShout(cat); // 输出: 喵喵~
	return 0;
}

我们再来看看像这样一段代码:

cpp 复制代码
template<typename Derived>
class Base {
public:
    constexpr Base() {};
    constexpr double interface() {
        return static_cast<Derived*>(this)->implementation();
    }

    double _val = 1.0;
};

class Derived1 : public Base<Derived1> {
public:
    constexpr Derived1(double v) { _val = v; };
    constexpr double implementation()
    { 
        /* 实现1 */ 
        return _val/2.0;
    }
};

class Derived2 : public Base<Derived2> {
public:
    constexpr Derived2(double v) { _val = v; };
    constexpr double implementation()
    {
        /* 实现2 */
        return _val+1.0;
    }
};

template<typename T>
constexpr double implementation_t(T* b)
{
    return b->interface();
}

constexpr double implementation()
{
    Derived1 *d1 = new Derived1(6.0);
    Derived2 *d2 = new Derived2(2.5);
    double vd1 = implementation_t(d1);
    double vd2 = implementation_t(d2);
    delete d1;
    delete d2;
    return vd1 + vd2;
}

int main()
{
    constexpr double v = implementation();
}

可以看出在哪一步体现出来了类似多态的效果了吗?
C++20的概念可以用来约束模板参数,从而实现更安全和更灵活的编译时多态性。

例如,我们可以定义一个Animal概念,该概念要求一个类型必须有一个shout成员函数。然后,我们可以定义一个泛型函数,该函数接受一个Animal对象:

cpp 复制代码
#include <iostream>
using namespace std;

class Dog {
public:
	void shout() { cout << "汪汪!" << endl; }
};

class Cat {
public:
	void shout() { cout << "喵喵~" << endl; }
};

class Mouse {
public:
	//void shout() { cout << "喵喵~" << endl; }
};

template<typename T>
concept Animal = requires(T t) {
	{ t.shout() } -> std::same_as<void>;
};
template<Animal T>
void animalShout(T& drawable) {
	drawable.draw();
}

int main() {
	Dog dog;
	Cat cat;

	animalShout(dog); // 输出: 汪汪!
	animalShout(cat); // 输出: 喵喵~

	//animalShout(Mouse); // 编译无法通过
	return 0;
}
C++23:编译时编程的全面能力
cpp 复制代码
#include <iostream>
using namespace std;

template<typename T>
struct Processor {
    constexpr auto process(const T& value) const {
        if consteval {
            // 编译时优化版本
            return compile_time_process(value);
        }
        else {
            // 运行时版本
            return runtime_process(value);
        }
    }

private:
    constexpr auto compile_time_process(const T& value) const {
        // 复杂的编译时计算
        return value * value;  // 示例
    }

    auto runtime_process(const T& value) const {
        // 可能使用SIMD或其他运行时优化
        return value * value;
    }
};

int main() {
    constexpr Processor<int> p;
    constexpr auto ct_result = p.process(5);  // 使用编译时路径
    int x = 10;
    auto rt_result = p.process(x);            // 使用运行时路径
    
	return 0;
}
二、主要解决的问题深度分析

constexpr的引入是C++发展史上的一个里程碑,它系统性解决了多个长期存在的痛点。让我们从实际编程问题出发,深入分析每个问题的背景和解决方案。
(1)解决"两套代码"问题:运行时与编译时逻辑分离

问题表现:重复实现相同逻辑

问题根源:

  • 相同算法需要两种实现
  • 模板元编程语法晦涩,难以调试
  • 维护成本翻倍,容易产生不一致
    示例代码:
cpp 复制代码
// C++03时代:必须维护两套代码
// 运行时版本
int runtime_fibonacci(int n) {
    if (n <= 1) return n;
    return runtime_fibonacci(n - 1) + runtime_fibonacci(n - 2);
}

// 编译时版本(模板元编程)
template<int N>
struct Fibonacci {
    static const int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};
template<> struct Fibonacci<0> { static const int value = 0; };
template<> struct Fibonacci<1> { static const int value = 1; };

int main() {
    // 使用
    int rt_result = runtime_fibonacci(10);          // 运行时计算
    int ct_result = Fibonacci<10>::value;           // 编译时计算
	return 0;
}
cpp 复制代码
// constexpr解决方案:统一实现
// C++11/14:一套代码,两种用途
constexpr int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    // 使用
    constexpr int ct_value = fibonacci(10);  // 编译时计算
    int user_input = 6;
    int rt_value = fibonacci(user_input);    // 运行时计算
    int array[fibonacci(5)];                 // 数组大小=5
	return 0;
}

核心价值:DRY原则(Don't Repeat Yourself)的极致体现,减少代码重复和错误。
2)解决"魔数"(Magic Numbers)和宏滥用问题

问题表现:硬编码与宏的局限
示例代码:

cpp 复制代码
// 传统方式:宏和硬编码
#define BUFFER_SIZE 1024
#define CACHE_LINE 64
#define MAX_RETRIES 3

class NetworkBuffer {
    char buffer[BUFFER_SIZE];  // 数组大小
    int alignment = CACHE_LINE; // 对齐值
    // 问题:宏没有类型,没有作用域,难以调试
};

// 或者使用枚举
enum Constants {
    BufferSize = 1024,
    CacheLine = 64,
    MaxRetries = 3
};
// 枚举的问题:类型是int,不能是其他类型(如double)
cpp 复制代码
// constexpr解决方案:类型安全的常量表达式
// 现代C++:类型安全,可计算的常量
namespace constants {
    constexpr size_t buffer_size = 1024;
    constexpr size_t cache_line = 64;
    constexpr int max_retries = 3;
    constexpr double pi = 3.141592653589793;
    
    // 甚至可以计算
    constexpr size_t aligned_buffer_size(size_t size) {
        return (size + cache_line - 1) & ~(cache_line - 1);
    }
}

class NetworkBuffer {
    char buffer[constants::buffer_size];
    static constexpr int alignment = constants::cache_line;
    char aligned_buffer[constants::aligned_buffer_size(512)];
};

核心价值

  • 类型安全,作用域明确
  • 可计算,不只是字面量
  • 支持浮点数等非整数类型
    (3)解决模板参数限制问题

问题表现:模板参数只能是编译时常量
示例代码

cpp 复制代码
// C++03:模板参数必须是编译时已知的
template<int Size>
class FixedArray {
    int data[Size];
public:
    FixedArray() {}
};

int main() {
    // 但如何获取Size?只能:
    enum { MySize = 100 };
    FixedArray<MySize> arr1;  // 可以

    int size = 5;  // 运行时计算
    //FixedArray<size> arr2;     // 错误!size不是编译时常量
	return 0;
}
cpp 复制代码
// constexpr解决方案:函数作为编译时参数源
#include <array>
// C++11起:constexpr函数生成编译时常量
constexpr int calculate_buffer_size(int multiplier) {
    return 64 * multiplier;  // 可包含复杂逻辑
}

template<int Size>
class FixedArray {
    std::array<int, Size> data;
public:
    constexpr FixedArray() = default;
};


int main() {
    // 使用
    FixedArray<calculate_buffer_size(4)> arr1;    // Size=256
    FixedArray<calculate_buffer_size(10)> arr2;   // Size=640

    constexpr int precomputed_size = calculate_buffer_size(2);
    FixedArray<precomputed_size> arr3;           // Size=128
	return 0;
}

核心价值:模板参数可以是函数计算结果,极大增强了模板的灵活性。
(4)解决编译时计算与运行时计算的性能矛盾

问题表现:性能与灵活性的取舍
示例代码:

cpp 复制代码
// 传统方式:要么静态表(浪费内存),要么运行时计算(浪费CPU)
// 选项1:预计算表(静态,浪费内存)
const int sin_table[360] = {
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // ... 手工维护
};

// 选项2:运行时计算(每次调用都计算)
double runtime_sin(double angle) {
    return sin(angle * 3.14159 / 180.0);  // 每次都算
}

// 嵌入式场景特别痛苦:内存紧张但又需要性能
cpp 复制代码
// constexpr解决方案:按需生成
#include <math.h>
#include <array>

// 现代C++:需要时才生成,最优平衡
constexpr double compile_time_sin(int degrees) {
    // 使用泰勒展开等算法
    double radians = degrees * 3.1415926535 / 180.0;
    auto pow_m = [](int v, int s) ->int
        {
            int r = v;
            for (int i = 1; i < s; i++)
            {
                r *= v;
            }
            return r;
        };
    return radians - pow_m(radians, 3) / 6 + pow_m(radians, 5) / 120;
    //return radians - pow(radians, 3) / 6 + pow(radians, 5) / 120; //pow 函数可能还不支持
}

template<int... Degrees>
constexpr auto generate_sin_table() {
    return std::array<double, sizeof...(Degrees)>{
        compile_time_sin(Degrees)...
    };
}



int main() {
    // 使用:只生成需要的部分
    constexpr auto sin_table = generate_sin_table<0, 15, 30, 45, 60, 75, 90>();

    // 运行时仍然可以使用同一函数
    int user_angle = 30;
    double rt_value = compile_time_sin(user_angle);
	return 0;
}

核心价值

  • 消除预计算表的维护成本
  • 避免重复运行时计算
  • 内存和CPU的最优平衡
    (5)解决编译时验证的缺失问题

问题表现:错误只能在运行时发现

cpp 复制代码
// 传统方式:配置错误要到运行时才崩溃
struct Config {
    int width;
    int height;
    int fps;
};

Config load_config() {
    Config cfg;
    cfg.width = 1920;
    cfg.height = 1080;
    cfg.fps = 120;  // 错误:显示不支持120fps
    
    // 验证逻辑
    if (cfg.fps > 60) {
        throw "Unsupported frame rate!";  // 运行时才抛出
    }
    return cfg;
}

// 用户可能在部署后才发现问题
cpp 复制代码
// constexpr解决方案:编译时验证
// 现代C++:编译时就发现问题
struct Config {
    int width;
    int height;
    int fps;
    
    constexpr Config(int w, int h, int f) 
        : width(w), height(h), fps(f) {
        // 编译时验证
        if (fps > 60) {
            throw "Unsupported frame rate!";  // 编译时错误
        }
        if (width <= 0 || height <= 0) {
            throw "Invalid dimensions!";      // 编译时错误
        }
    }
};

// 使用
constexpr Config cfg(1920, 1080, 30);    // OK
// constexpr Config bad_cfg(1920, 1080, 120);  // 编译错误!

// 还可以结合static_assert
static_assert(Config(1920, 1080, 30).fps == 30, "Verification");

核心价值

  • 错误在编译期发现,成本接近于0
  • 提供更强的API约束
  • 适用于安全关键系统
    根本哲学转变:从"写两套代码实现不同优化"到"写一套代码让编译器选择最佳执行时机"。
一、consteval/constinit/constptr 对比分析

对比分析表:

特性 引入版本 用途 要求 适用对象
consteval C++20 创建立即函数,必须在编译期求值 函数体必须能在编译期执行 函数
constinit C++20 确保静态/线程局部变量的编译期初始化 初始值必须是常量表达式 静态/线程局部变量
constexpr C++11/14/17 函数/变量可以在编译期求值 consteval宽松 函数、变量
  1. consteval - 编译期强制求值
cpp 复制代码
consteval int square(int x) {  // 必须编译期求值
    return x * x;
}

int main() {
    constexpr int a = square(5);    //  OK
    // int b = square(rand());      //  错误:运行时值
    // consteval函数只能用于常量表达式
}

核心 :编译期执行的保证
不能:获取地址、有静态变量、有try-catch等
2. constinit - 编译期初始化保证

cpp 复制代码
constexpr int get_value() { return 42; }

//  编译期初始化
constinit int global = get_value();  // 必须是常量表达式

//  错误:初始化值不是常量表达式
// constinit int bad = std::rand();

void func() {
    //  错误:constinit只能用于静态存储期
    // constinit int local = 10;
    
    static constinit int static_var = 100;  // OK
}

核心 :防止静态初始化顺序问题
必须:静态/线程存储期,常量表达式初始化
3. constexpr - 编译期求值能力(更宽松)

cpp 复制代码
constexpr int factorial(int n) {  // 可以编译期或运行期求值
    return (n <= 1) ? 1 : n * factorial(n - 1);
}

int main() {
    constexpr int a = factorial(5);  //  编译期求值
    int runtime = 10;
    int b = factorial(runtime);      //  运行期求值(允许)
}

通过这么多的讲解,相信大家对一开始的问题心中已经有了自己的答案了吧?

我的回答:为了在编译期执行原本只能在运行期进行的计算,解决编译时常量表达能力不足和性能优化受限的问题,从而实现对关键计算的零开销抽象、编译时验证和极致性能优化。

相关推荐
im_AMBER2 小时前
Leetcode 83 使数组平衡的最少移除数目中等相关标签 | 尽可能使字符串相等
数据结构·c++·笔记·学习·算法·leetcode
XFF不秃头2 小时前
力扣刷题笔记-组合总和
c++·笔记·leetcode
xu_yule2 小时前
算法基础(图论)—拓扑排序
c++·算法·动态规划·图论·拓扑排序·aov网
我是华为OD~HR~栗栗呀2 小时前
(华为od)21届-Python面经
java·前端·c++·python·华为od·华为·面试
kyle~2 小时前
导航---Nav2导航框架概览
c++·机器人·ros2·导航
DolphinDB智臾科技2 小时前
如何用脚本榨出C++级性能?微秒级低延时系统优化深度解析
大数据·c++·时序数据库·低延时·dolphindb
小龙报2 小时前
【初阶数据结构】从 “数组升级” 到工程实现:动态顺序表实现框架的硬核拆解指南
c语言·数据结构·c++·算法·机器学习·信息与通信·visual studio
我不是8神2 小时前
序列化与反序列化详解
c++
MSTcheng.3 小时前
【C++STL】map / multimap 保姆级教程:从底层原理到实战应用!
开发语言·c++·stl·map·红黑树