跳到正文

Wiki

constexpr

約 6 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

1.本節解決什麼問題

有些計算在編譯時就已經知道結果,但如果用普通函式,卻要等到執行時才計算——浪費了 CPU 時間。此外,某些場景(陣列大小、模板引數、static_assert)必須使用編譯期常量,普通變數做不到。

constexpr 讓函式和變數在編譯期就能求值,把執行時開銷轉移到編譯期,還能用在"必須編譯期常量"的地方。

2.這個特性是什麼

constexpr 宣告一個變數或函式可以在編譯期求值。從 C++11 開始引入,後續標準不斷增強。

C++ 版本 支援
C++11 簡單 constexpr 函式(一條 return 語句)、constexpr 變數
C++14 constexpr 函式支援多條語句、迴圈
C++17 constexpr lambda
C++20 consteval(必須在編譯期執行)、更強的 constexpr
C++23 constexpr 支援更多標準庫

3.C++ 標準版本

C++11(隨後的每個版本都有增強)

4.需要的標頭檔案

不需要額外標頭檔案。constexpr 是語言關鍵字。

5.基本語法

// constexpr 变量:值在编译期确定
constexpr int size = 100;

// constexpr 函数:可以在编译期执行
constexpr int square(int n)
{
    return n * n;
}

// constexpr 变量可以用编译期函数初始化
constexpr int val = square(5);  // 编译期计算

// C++20 consteval:必须在编译期执行(不能运行时调用)
consteval int cube(int n)
{
    return n * n * n;
}

6.const vs constexpr

方面 const constexpr
含義 變數不可修改 變數或函式可在編譯期求值
初始化 可在執行時初始化 必須在編譯期初始化
函式 只能修飾成員函式 函式可在編譯期執行
用途 防止變數被修改 編譯期計算,提高效率
示例 const int x = get_value(); constexpr int x = 5 * 3;

7.constexpr、consteval、constinit 的區別

這三個詞看起來都和"常量"有關,但解決的問題不同。初學時先掌握 constexpr,再理解 C++20 的 constevalconstinit

關鍵字 含義 典型場景
constexpr 可以在編譯期求值,也可以在執行期呼叫 通用編譯期函式、常量表達式
consteval 必須在編譯期求值 生成編譯期 ID、編譯期校驗
constinit 保證靜態物件在編譯期初始化,但物件不一定是 const 避免全域性變數動態初始化順序問題

一句話區分:constexpr 偏"能不能算",consteval 偏"必須現在算",constinit 偏"初始化時機必須早"。

8.示例程式碼

8.1.示例 1:constexpr 變數——必須編譯期確定

#include <iostream>

int main()
{
    constexpr int size = 10;        // 编译期常量
    int arr[size];                  // ✅ 可以用作数组大小

    const int runtime_size = size;  // const 可以用编译期常量初始化
    int arr2[runtime_size];         // ✅ 也可以用作数组大小(因为初始化值是 constexpr)

    int n = 5;
    // constexpr int s2 = n;        // ❌ n 是运行时变量,不能初始化 constexpr
    const int s3 = n;               // ✅ const 可以在运行时初始化
    // int arr3[s3];                // ❌ s3 不是编译期常量,不能用作数组大小

    std::cout << "size = " << size << "\n";

    return 0;
}

執行結果

size = 10

8.2.示例 2:在示例 1 基礎上,constexpr 函式

#include <iostream>

// constexpr 函数:编译期可执行
constexpr int factorial(int n)
{
    int result = 1;
    for (int i = 1; i <= n; ++i)  // C++14 起支持循环
    {
        result *= i;
    }
    return result;
}

int main()
{
    // 编译期计算:factorial(5) 在编译时就得到 120
    constexpr int f5 = factorial(5);
    std::cout << "factorial(5) = " << f5 << "\n";

    // 也可运行时调用
    int n;
    std::cout << "Enter a number: ";
    std::cin >> n;
    std::cout << "factorial(" << n << ") = " << factorial(n) << "\n";

    return 0;
}

執行結果

factorial(5) = 120
Enter a number: 4
factorial(4) = 24

8.3.示例 3:在示例 2 基礎上,constexpr 用於模板和編譯期檢查

#include <iostream>

// constexpr 函数:编译期判断是否为质数
constexpr bool is_prime(int n)
{
    if (n <= 1) return false;
    for (int i = 2; i * i <= n; ++i)
    {
        if (n % i == 0) return false;
    }
    return true;
}

// 编译期静态断言
static_assert(is_prime(2), "2 is prime");
static_assert(is_prime(7), "7 is prime");
static_assert(!is_prime(9), "9 is NOT prime");

// 编译期生成质数数组
constexpr int get_nth_prime(int n)
{
    int count = 0;
    int num = 1;
    while (count < n)
    {
        ++num;
        if (is_prime(num))
        {
            ++count;
        }
    }
    return num;
}

int main()
{
    constexpr int primes[] = {
        get_nth_prime(1),  // 2
        get_nth_prime(2),  // 3
        get_nth_prime(3),  // 5
        get_nth_prime(4),  // 7
        get_nth_prime(5)   // 11
    };

    std::cout << "First 5 primes: ";
    for (int p : primes)
    {
        std::cout << p << " ";
    }
    std::cout << "\n";

    return 0;
}

執行結果

First 5 primes: 2 3 5 7 11 

8.4.示例 4:C++17 constexpr lambda

#include <iostream>

int main()
{
    // constexpr lambda:编译期可调用
    constexpr auto square = [](int x) constexpr {
        return x * x;
    };

    constexpr int result = square(5);  // 编译期计算
    std::cout << "square(5) = " << result << "\n";

    // constexpr lambda 也可运行时调用
    int n = 7;
    std::cout << "square(" << n << ") = " << square(n) << "\n";

    return 0;
}

執行結果

square(5) = 25
square(7) = 49

8.5.示例 5:在示例 4 基礎上,constexpr 可以執行期呼叫,consteval 不行

#include <iostream>

constexpr int square(int x)
{
    return x * x;
}

consteval int compile_time_square(int x)
{
    return x * x;
}

int main()
{
    constexpr int a = square(5);              // 编译期计算
    int n = 7;
    int b = square(n);                        // 运行期调用,也允许

    constexpr int c = compile_time_square(6); // 必须编译期计算
    // int d = compile_time_square(n);        // ❌ 编译错误:n 不是编译期常量

    std::cout << "a = " << a << "\n";
    std::cout << "b = " << b << "\n";
    std::cout << "c = " << c << "\n";

    return 0;
}

執行結果

a = 25
b = 49
c = 36

9.執行結果

見上方每個示例的"執行結果"。

10.示例中的關鍵語法解釋

示例 講了什麼 新出現的語法 為什麼這樣寫 注意事項
示例 1 constexpr vs const constexpr int size = 10;、陣列大小 constexpr 必須編譯期初始化,可以用作陣列大小 const 可以在執行時初始化,不保證編譯期
示例 2 constexpr 函式 迴圈在 constexpr 函式中 同一個函式可編譯期呼叫也可執行時呼叫 編譯期呼叫時所有引數也必須是編譯期常量
示例 3 constexpr 用於 static_assert static_assert() 編譯期檢查,不通過直接編譯失敗 static_assert 的引數必須是編譯期常量
示例 4 constexpr lambda constexpr auto f = [](int x) constexpr {...}; lambda 也能編譯期求值 C++17 起支援,兩個 constexpr 都要寫
示例 5 constexpr vs consteval consteval consteval 函式只能編譯期呼叫 C++20 才有 consteval

11.常見錯誤

錯誤 1:把執行時變數賦給 constexpr

int n = 5;
constexpr int x = n;  // ❌ n 不是编译期常量

正確做法:const int x = n;(用 const 而不是 constexpr)。

錯誤 2:constexpr 函式假設所有情況都能編譯期求值

constexpr int divide(int a, int b)
{
    return a / b;  // 编译期 b=0 会导致编译错误
}
constexpr int x = divide(10, 0);  // ❌ 编译错误!

正確做法:編譯期呼叫時注意引數合法性。

錯誤 3:constexpr 函式內部有未定義行為

constexpr int get(int* p)
{
    return *p;  // 编译期不能解引用空指针
}
constexpr int x = get(nullptr);  // ❌ 编译错误!

正確做法:constexpr 函式中避免未定義行為。

錯誤 4:以為 constexpr 函式一定在編譯期執行

constexpr int square(int x) { return x * x; }

int n = 5;
int y = square(n);  // ✅ 这是运行期调用,不是编译期调用

正確做法:需要強制編譯期時,把結果放進 constexpr 變數、static_assert、模板引數中;C++20 起也可以使用 consteval 函式。

12.使用建議

  1. 能用 constexpr 表達的就用 constexpr:把執行時計算移到編譯期,程式效能更好。
  2. constexpr 函式既編譯期又執行期:不必寫兩份程式碼。
  3. static_assert 做編譯期檢查:配合 constexpr 函式非常強大。
  4. constexpr 不是"更快"的魔法:對於小函式,編譯器本來就會最佳化。但對於常量表、預計算等場景很有用。
  5. 需要"必須編譯期"才考慮 consteval:普通工具函式優先寫 constexpr,限制更少。

13.小結

  • constexpr 變數必須在編譯期確定,可以用作陣列大小、模板引數等。
  • constexpr 函式可以在編譯期執行(引數也都是 constexpr 時)。
  • constexpr 函式也可以執行期呼叫;consteval 才是必須編譯期呼叫。
  • const 強調"不可修改",constexpr 強調"編譯期求值"。
  • C++17 起 lambda 可以用 constexpr
  • static_assert + constexpr 函式 = 編譯期安全網。