9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C++ TMP (テンプレートメタプログラミング) シリーズ

Part1 constexpr Part2 Concepts Part3 Variadic Part4 型リスト Part5 正規表現
👈 Now

はじめに

C++11で導入されたconstexpr。最初は「定数式で使える関数」程度だった。

でもC++14、C++17、C++20と進化して、今やコンパイル時に何でもできるレベルに。

このシリーズでは、Modern C++のテンプレートメタプログラミングを体系的に解説する。

constexprの進化

バージョン できること
C++11 1行のreturn文のみ
C++14 ローカル変数、ループ、分岐
C++17 if constexpr、constexpr lambda
C++20 仮想関数、try-catch、動的メモリ

C++11 constexpr

最初のconstexprは制約が厳しかった。

// C++11: return文1つだけ
constexpr int factorial_11(int n) {
    return n <= 1 ? 1 : n * factorial_11(n - 1);
}

// OK
constexpr int fact5 = factorial_11(5);  // 120

// NG: ローカル変数が使えない
// constexpr int sum_11(int n) {
//     int result = 0;        // エラー!
//     for (int i = 1; i <= n; ++i) {
//         result += i;
//     }
//     return result;
// }

三項演算子と再帰で頑張るしかなかった。

C++14 constexpr

ローカル変数とループが使えるようになった!

// C++14: ループが使える!
constexpr int factorial_14(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

constexpr int sum_to_n(int n) {
    int result = 0;
    for (int i = 1; i <= n; ++i) {
        result += i;
    }
    return result;
}

constexpr int fact10 = factorial_14(10);
constexpr int sum100 = sum_to_n(100);  // 5050

制限

ただし、まだ以下は使えない:

  • 動的メモリ確保
  • 仮想関数
  • try-catch

C++17 constexpr

if constexpr

コンパイル時に分岐を選択できる。

template<typename T>
constexpr auto process(T value) {
    if constexpr (std::is_integral_v<T>) {
        // 整数型の場合のみコンパイルされる
        return value * 2;
    } else if constexpr (std::is_floating_point_v<T>) {
        // 浮動小数点型の場合のみコンパイルされる
        return value + 0.5;
    } else {
        // それ以外
        return value;
    }
}

constexpr auto result_int = process(10);      // 20
constexpr auto result_float = process(3.14);  // 3.64

通常のifとの違い

template<typename T>
void print_all(T value) {
    // 通常のif: 両方のブランチがコンパイルされる
    if (std::is_integral_v<T>) {
        std::cout << value * 2;
    } else {
        std::cout << value.to_string();  // 整数型でもコンパイルエラー!
    }
}

template<typename T>
void print_all_constexpr(T value) {
    // if constexpr: 選ばれたブランチのみコンパイル
    if constexpr (std::is_integral_v<T>) {
        std::cout << value * 2;
    } else {
        std::cout << value.to_string();  // 整数型ではコンパイルされない
    }
}

constexpr lambda

// C++17からラムダもconstexprにできる
constexpr auto square = [](int x) { return x * x; };
constexpr int result = square(5);  // 25

// 明示的なconstexpr指定も可能
constexpr auto cube = [](int x) constexpr { return x * x * x; };

C++20 constexpr

大幅な拡張!

constexpr仮想関数

struct Base {
    constexpr virtual int value() const { return 1; }
    constexpr virtual ~Base() = default;
};

struct Derived : Base {
    constexpr int value() const override { return 2; }
};

constexpr int get_value() {
    Derived d;
    Base& b = d;
    return b.value();  // 仮想関数呼び出し
}

constexpr int v = get_value();  // 2

constexpr try-catch

constexpr int safe_divide(int a, int b) {
    try {
        if (b == 0) {
            throw "Division by zero";  // コンパイル時例外
        }
        return a / b;
    } catch (...) {
        return 0;
    }
}

// ただし、実際に例外が投げられるとコンパイルエラー
constexpr int result = safe_divide(10, 2);  // OK: 5
// constexpr int error = safe_divide(10, 0);  // エラー!

constexpr動的メモリ

constexpr int sum_vector() {
    std::vector<int> vec;  // コンパイル時にvector!
    
    for (int i = 1; i <= 10; ++i) {
        vec.push_back(i);
    }
    
    int sum = 0;
    for (int v : vec) {
        sum += v;
    }
    
    return sum;
}

constexpr int result = sum_vector();  // 55

制約: コンパイル時に確保したメモリは、コンパイル時に解放しなければならない。

// NG: メモリがコンパイル時に残る
constexpr auto* get_ptr() {
    return new int(42);  // コンパイルエラー!
}

// OK: メモリを解放して値を返す
constexpr int get_value() {
    auto* p = new int(42);
    int value = *p;
    delete p;
    return value;
}

constexpr std::string

constexpr std::string make_greeting(std::string_view name) {
    std::string result = "Hello, ";
    result += name;
    result += "!";
    return result;
}

// C++20: std::stringがconstexprに
constexpr auto greeting = make_greeting("World");  // "Hello, World!"

consteval: 必ずコンパイル時評価

constexprは実行時評価許容する。
constevalコンパイル時のみ

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

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

int main() {
    int x = 5;
    
    // constexpr: 実行時もOK
    int a = square(x);  // 実行時評価
    
    // consteval: コンパイル時のみ
    // int b = square_compile_time(x);  // エラー!実行時変数は不可
    
    constexpr int c = square_compile_time(5);  // OK
}

使い分け

種類 用途
constexpr コンパイル時/実行時どちらでも
consteval コンパイル時専用(より強い保証)
constinit 静的初期化の保証

constinit: 静的初期化の保証

グローバル変数の初期化順序問題を解決。

// 問題: 初期化順序が不定
struct Config {
    static int value;
};
int Config::value = compute_value();  // いつ呼ばれる?

// 解決: constinit
constinit int global_value = 42;  // 必ず静的初期化

// constexprである必要はないが、静的初期化可能でなければならない
constinit thread_local int tls_value = 0;

実践例1: コンパイル時CRC32

constexpr uint32_t crc32_table[256] = []() consteval {
    std::array<uint32_t, 256> table{};
    
    for (uint32_t i = 0; i < 256; ++i) {
        uint32_t crc = i;
        for (int j = 0; j < 8; ++j) {
            crc = (crc >> 1) ^ (0xEDB88320 & (-(crc & 1)));
        }
        table[i] = crc;
    }
    
    return table;
}();

constexpr uint32_t crc32(std::string_view data) {
    uint32_t crc = 0xFFFFFFFF;
    
    for (char c : data) {
        crc = crc32_table[(crc ^ static_cast<uint8_t>(c)) & 0xFF] ^ (crc >> 8);
    }
    
    return crc ^ 0xFFFFFFFF;
}

// コンパイル時にハッシュ計算
constexpr uint32_t hash = crc32("Hello, World!");

実践例2: コンパイル時ソート

template<typename T, size_t N>
constexpr std::array<T, N> sorted(std::array<T, N> arr) {
    // バブルソート(コンパイル時間は気にしない)
    for (size_t i = 0; i < N - 1; ++i) {
        for (size_t j = 0; j < N - i - 1; ++j) {
            if (arr[j] > arr[j + 1]) {
                auto temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    return arr;
}

constexpr std::array<int, 5> unsorted = {5, 2, 8, 1, 9};
constexpr std::array<int, 5> sorted_arr = sorted(unsorted);
// sorted_arr = {1, 2, 5, 8, 9}

実践例3: コンパイル時JSON検証

enum class JsonError {
    None,
    UnexpectedChar,
    UnterminatedString,
    // ...
};

constexpr JsonError validate_json(std::string_view json) {
    size_t pos = 0;
    
    auto skip_whitespace = [&]() {
        while (pos < json.size() && 
               (json[pos] == ' ' || json[pos] == '\n' ||
                json[pos] == '\t' || json[pos] == '\r')) {
            pos++;
        }
    };
    
    // 簡略化した検証ロジック
    skip_whitespace();
    if (pos >= json.size()) {
        return JsonError::UnexpectedChar;
    }
    
    if (json[pos] == '{' || json[pos] == '[') {
        // オブジェクトまたは配列
        return JsonError::None;  // 実際はもっと詳細にチェック
    }
    
    return JsonError::UnexpectedChar;
}

// コンパイル時にJSONを検証
static_assert(validate_json(R"({"key": "value"})") == JsonError::None);
static_assert(validate_json(R"([1, 2, 3])") == JsonError::None);
// static_assert(validate_json("invalid") == JsonError::None);  // エラー!

コンパイル時計算のデバッグ

// static_assertでデバッグ
constexpr int compute_something(int x) {
    int result = 0;
    for (int i = 0; i < x; ++i) {
        result += i * i;
    }
    return result;
}

// 期待値をチェック
static_assert(compute_something(0) == 0);
static_assert(compute_something(1) == 0);
static_assert(compute_something(3) == 5);  // 0 + 1 + 4 = 5
static_assert(compute_something(5) == 30); // 0 + 1 + 4 + 9 + 16 = 30

まとめ

キーワード C++ 用途
constexpr 11+ コンパイル時/実行時両用
if constexpr 17 コンパイル時分岐
constexpr lambda 17 コンパイル時ラムダ
consteval 20 コンパイル時専用
constinit 20 静的初期化保証

C++20のconstexprは強力すぎて、もはや実行時コードをそのままコンパイル時に持っていけるレベル。

次回はコンセプトを使った型制約について解説する。

続きが気になったら、いいね・ストックしてもらえると嬉しいです!

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?