• C++ constexpr, consteval, 和 constinit简要介绍


    constexpr

    constexpr是C++中的一个关键字,用于定义常量表达式。这些表达式在编译时就可以计算出结果,而不是在运行时。

    constexpr

    constexpr 可以修饰变量. 此时,变量必须在编译时就能确定其值.

    // 初始化常量表达式
    constexpr int min_size = 10;             // OK, 字面量初始化
    constexpr int max_size = min_size + 10;  // OK, 常量表达式
    min_size = 20;                           // Error, 常量不能被修改
    

    可以像使用常量一样使用 constexpr 变量.

    constexpr int size = 10;
    int c_arr[size] = {0};              // OK
    std::array<int, size> array = {0};  // OK
    

    除了整型, 其他类型也可以使用 constexpr 修饰.

    constexpr double root_of_2 = 1.41421356237;
    constexpr const char* hello = "Hello, World!";
    constexpr std::string_view str_view{hello};
    

    如果你想定义一个constexpr的类变量. 你需要确保类的构造函数是constexpr的.

    // constexpr 类实例
    class Point {
     public:
      constexpr Point(int x, int y) : x_(x), y_(y) {}
      constexpr int x() const { return x_; }
      constexpr int y() const { return y_; }
    
     private:
      int x_;
      int y_;
    };
    constexpr Point origin{0, 0};
    constexpr Point dst{1, 1};
    

    constexpr 函数和lambda

    constexpr 修饰函数表示该函数在编译时就能计算出结果.

    constexpr int square(int x) { return x * x; }
    
    int main() {
      constexpr int a = 3;
      constexpr int b = 4;
      constexpr int c = square(a) + square(b);  // OK, a 和 b 是常量
      int d = square(5);                        // OK, d 不要求是常量
      int e = 3;
      constexpr int f = square(e);  // Error, e 不是常量. 参数必须是常量
    
      auto abs = [](int x) constexpr -> int { return x < 0 ? -x : x; };
      constexpr int g = abs(-1);  // OK
    
      return abs(0);
    }
    

    if constexpr编译时分支

    if constexpr引入的主要原因是为了提高C++模板编程的能力和效率,具体包括:

    1. 编译时分支决策if constexpr允许在编译时根据模板参数或其他编译时可知的条件进行条件分支,这意味着可以在编译时决定哪些代码会被编译进最终的程序中。这对于模板元编程尤其重要,因为它允许基于类型特性进行条件编译,从而避免了运行时的分支判断,提高了程序的效率。

    2. 简化模板代码:在引入if constexpr之前,实现基于类型的条件编译通常需要使用模板特化或SFINAE(替换失败不是错误)技术,这些技术不仅代码复杂,而且对于初学者来说难以理解。if constexpr简化了这一过程,使得基于类型条件的代码分支更加直观和易于编写。

    3. 避免无效代码实例化:在模板编程中,某些代码路径可能对于特定的模板参数是无效的。使用if constexpr可以确保只有有效的代码路径会被实例化,从而避免编译错误。

    4. 优化性能:由于if constexpr在编译时就决定了代码的执行路径,它可以帮助编译器生成更优化的代码。对于不满足条件的分支,由于它们根本不会被编译,因此可以减少最终程序的大小,并提高运行时性能。

    5. 增强代码可读性和维护性if constexpr使得条件编译的意图更加明显,提高了代码的可读性。同时,由于减少了模板特化和SFINAE的需要,也使得代码更容易维护。

    #include 
    #include 
    #include 
    
    template <typename T>
    void process(const T& value) {
      if constexpr (std::is_integral<T>::value) {
        std::cout << "Processing integral type: " << value << std::endl;
      } else if constexpr (std::is_floating_point<T>::value) {
        std::cout << "Processing floating point type: " << value << std::endl;
      } else {
        std::cout << "Processing other types" << std::endl;
      }
    }
    
    template <typename T>
    void printPositiveOrNegative(const T& value) {
      if constexpr (std::is_signed<T>::value) {
        if (value < 0) {
          std::cout << "Negative" << std::endl;
        } else {
          std::cout << "Positive or zero" << std::endl;
        }
      } else {
        std::cout << "Always positive or zero" << std::endl;
      }
    }
    
    template <int N>
    constexpr int factorial() {
      if constexpr (N == 0) {
        return 1;
      } else {
        return N * factorial<N - 1>();
      }
    }
    
    template <typename T>
    void printOrCompute(const T& value) {
      if constexpr (std::is_same<T, std::string>::value) {
        std::cout << "String: " << value << std::endl;
      } else if constexpr (std::is_arithmetic<T>::value) {
        std::cout << "Arithmetic result: " << (value + value) << std::endl;
      } else {
        std::cout << "Other type" << std::endl;
      }
    }
    
    int main() {
      process(1);        // Processing integral type: 1
      process(3.14);     // Processing floating point type: 3.14
      process("hello");  // Processing other types
    
      printPositiveOrNegative(-1);  // Negative
      printPositiveOrNegative(0);   // Positive or zero
      printPositiveOrNegative(1u);  // Always positive or zero
    
      constexpr int result = factorial<5>();
      std::cout << "5! = " << result << std::endl;  // 5! = 120
    
      using namespace std::string_literals;  // 使用 string 字面量, 下一行才能使用
                                             // "hello"s
      printOrCompute("hello"s);  // String: hello
      printOrCompute("world");   // Other type
      printOrCompute(1);         // Arithmetic result: 2
      printOrCompute(3.14);      // Arithmetic result: 6.28
      return 0;
    }
    

    consteval

    consteval 是C++20中的一个新关键字,用于定义只能在编译时计算的函数。consteval 函数必须在编译时就能计算出结果,否则会导致编译错误。

    #include 
    
    // fib 函数用来求第n个Fibonacci数
    consteval int fib(int n) {
      int a = 0, b = 1;
      for (int i = 0; i < n; i++) {
        a = std::exchange(b, a + b);
      }
      return a;
    }
    
    const int K = 10;
    constexpr int cx = fib(0);  // OK
    const int c1 = fib(0);      // OK
    int g1 = fib(10);           // OK
    int g2 = fib(K);            // OK, K 是常量
    int g3 = fib(cx);           // OK, cx 是常量表达式
    
    int g5 = fib(g1);      // Error, g1 不是常量
    consteval int g6 = 1;  // Error, consteval 不能修饰变量
    
    int main() { return fib(0); }
    

    constinit

    constinit 是 C++20 中引入的一个新关键字,用于确保变量在程序启动前完成初始化。这对于需要在编译时就确定其值的全局或静态变量特别有用。

    constinit 关键字的提出是为了解决如下的问题:

    1. 确定性初始化:确保全局或静态变量在程序启动前完成初始化,提供了一种明确的方式来声明这些变量的初始化时机,从而避免了静态初始化顺序问题(SIOF)。

    2. 性能优化:与动态初始化相比,constinit确保了变量的初始化可以在编译时进行,减少了运行时的开销。这对于性能敏感的应用程序来说是一个重要的优势。

    3. 编译时检查constinit要求变量必须在编译时就能初始化。这种强制性的编译时检查可以避免运行时错误和不确定的行为,提高了代码的安全性和可靠性。

    4. constexprconsteval的互补constinit与C++20中的其他两个关键字constexprconsteval一起,提供了一套完整的工具,用于控制变量和函数的编译时行为。constexpr允许在编译时或运行时计算,consteval强制函数必须在编译时计算,而constinit确保变量在程序启动前初始化。

    consinit 的使用要求:

    1. 变量必须是全局变量或静态变量, 但不一定具有常量属性.
    2. 变量必须是用常量初始化的(常量字面值, constexprconsteval)。
    /* 全局变量 */
    constinit int global_var = 1;           // OK
    constinit static int global_max = 100;  // OK
    constinit const int global_min = 10;    // OK
    constinit int init_var = global_var;    // Error, 需要使用常量初始化
    
    int main() {
      constinit static int static_var = 9;  // OK
      constinit int local_var = 8;          // Error, local_var 不是 static
      return 0;
    }
    
  • 相关阅读:
    Win11远程协助灰色无法勾选?Win11远程协助不能选择的解决方法
    2000亿元贴息贷款,医疗系统上云,解锁医护协同新玩法
    简单入门编写html登录界面
    Java入门必备基础知识
    基于JAVA农产品的物流信息服务平台计算机毕业设计源码+系统+mysql数据库+lw文档+部署
    基于残差学习的卷积神经网络图像去噪研究-含Matlab代码
    多项式求和算法对比
    8. 无线体内纳米网:基于蓝牙LE接口的数字ID系统
    Spring Boot框架的原理及应用详解(四)
    【SNMP】snmp trap 与介绍、安装、命令以及Trap的发送与接收java实现
  • 原文地址:https://blog.csdn.net/arong_xu/article/details/139881566