Μετάβαση στο περιεχόμενο

Προγραμματισμός constexpr, consteval & Compile-Time

Μετακινήστε τον υπολογισμό από το χρόνο εκτέλεσης στον χρόνο μεταγλώττισης χρησιμοποιώντας constexpr, consteval και constinit. Δημιουργήστε πίνακες αναζήτησης, επικυρώστε διαμορφώσεις και χρησιμοποιήστε κοντέινερ constexpr.

Προγραμματισμός μεταγλώττισης χρόνου στο C++

Το C++ σας επιτρέπει μοναδικά να εκτελείτε πραγματικό κώδικα σε χρόνο μεταγλώττισης. Αυτό σημαίνει ότι ο μεταγλωττιστής μπορεί να υπολογίσει τιμές, να επικυρώσει εισόδους, να δημιουργήσει πίνακες αναζήτησης και ακόμη και να εκτελέσει σύνθετους αλγόριθμους — όλα αυτά πριν από την αποστολή του προγράμματός σας. Το αποτέλεσμα ενσωματώνεται απευθείας στο δυαδικό ως σταθερές.

Η εξέλιξη του προγραμματισμού μεταγλώττισης στο C++:

- C++11: Παρουσιάστηκε τοconstexpr— απλές συναρτήσεις δήλωσης μίας επιστροφής
- C++14: Χαλαρόconstexpr— βρόχοι, τοπικές μεταβλητές, πολλαπλές δηλώσεις
- C++17:if constexpr— διακλάδωση χρόνου μεταγλώττισης που απορρίπτει μονοπάτια που δεν έχουν ληφθεί
- C++20:consteval(πρέπει να είναι χρόνος μεταγλώττισης),constinit(εξασφαλίζει ότι η στατική έναρξη είναι χρόνος μεταγλώττισης), δοχείαconstexpr(vector,string)
- C++23:if consteval— εντοπίστε εάν βρίσκεστε σε ένα συνεχές πλαίσιο αξιολόγησης

Συναρτήσεις constexpr

Μια συνάρτηση constexpr μπορεί να αξιολογηθεί κατά το χρόνο μεταγλώττισης ή χρόνου εκτέλεσης. Εάν καλείται με ορίσματα χρόνου μεταγλώττισης σε περιβάλλον που απαιτεί μια σταθερά (όρισμα προτύπου, μεταβλητήconstexpr,static_assert, μέγεθος πίνακα), ο μεταγλωττιστής την αξιολογεί κατά τη στιγμή της μεταγλώττισης. Διαφορετικά, εκτελείται κατά το χρόνο εκτέλεσης όπως μια κανονική λειτουργία.

constexpr_functions.cpp
#include <iostream>
#include <array>
#include <cstdint>

constexpr int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

constexpr int fibonacci(int n) {
    if (n <= 1) return n;
    int a = 0, b = 1;
    for (int i = 2; i <= n; ++i) {
        int next = a + b;
        a = b;
        b = next;
    }
    return b;
}

int main() {
    // Compile-time evaluation: used in constexpr context
    constexpr int fact10 = factorial(10);  // computed at compile time
    static_assert(fact10 == 3628800);

    constexpr int fib10 = fibonacci(10);   // computed at compile time
    static_assert(fib10 == 55);

    // Compile-time array sizes
    std::array<int, factorial(5)> arr{};   // array of 120 elements

    // Runtime evaluation: argument not constexpr
    int n;
    std::cin >> n;
    std::cout << "factorial(" << n << ") = " << factorial(n) << '\n';
    // Same function, now runs at runtime
}

Consteval & Constinit (C++20)

Το consteval επισημαίνει μια συνάρτηση που πρέπει να αξιολογηθεί κατά το χρόνο μεταγλώττισης — η κλήση της με τιμές χρόνου εκτέλεσης είναι σφάλμα μεταγλώττισης. Αυτό ονομάζεται «άμεση λειτουργία». Το constinit διασφαλίζει ότι μια μεταβλητή με στατική ή νήμα-τοπική διάρκεια αποθήκευσης αρχικοποιείται κατά το χρόνο μεταγλώττισης, αποτρέποντας το "φιάσκο εντολής στατικής προετοιμασίας" — αλλά σε αντίθεση με τοconstexpr, η μεταβλητή μπορεί να τροποποιηθεί μετά την προετοιμασία.

consteval_constinit.cpp
#include <iostream>

// consteval: MUST be evaluated at compile time
consteval int square(int x) {
    return x * x;
}

// constinit: must be initialized at compile time,
// but can be modified at runtime
constinit int global_value = square(7);  // 49, computed at compile time

// constexpr variable: initialized at compile time AND immutable
constexpr int fixed_value = square(10);  // 100, compile time, cannot change

int main() {
    constexpr int a = square(5);  // OK: compile-time context
    static_assert(a == 25);

    // int x = 5;
    // int b = square(x);  // ERROR: consteval requires compile-time args

    // constinit variable can be modified after initialization
    global_value = 100;  // OK: runtime modification allowed
    std::cout << global_value << '\n'; // 100

    // fixed_value = 200;  // ERROR: constexpr variable is const
}

αν constexpr στην Πράξη

Το if constexpr (C++17) αξιολογεί μια συνθήκη κατά το χρόνο μεταγλώττισης και απορρίπτει τον κλάδο που δεν έχει ληφθεί εξ ολοκλήρου. Ο κλάδος που απορρίφθηκε δεν δημιουργείται - δεν χρειάζεται να είναι έγκυρος κώδικας για τα δεδομένα ορίσματα προτύπου. Αυτό είναι θεμελιωδώς διαφορετικό από ένα κανονικόif, όπου και οι δύο κλάδοι πρέπει να είναι έγκυροι.

Βασικοί κανόνες:

1. Η συνθήκη πρέπει να είναι μια παράσταση σταθεράς χρόνου μεταγλώττισης.
2. Σε ένα πρότυπο, ο κλάδος που απορρίφθηκε δεν έχει δημιουργηθεί για τα τρέχοντα ορίσματα προτύπου.
3. Εκτός προτύπου, και οι δύο κλάδοι πρέπει να εξακολουθούν να είναι συντακτικά έγκυροι (απλώς δεν θα εκτελεστούν).
4. Τοif constexprμπορεί να εμφανιστεί σε κανονικές συναρτήσεις, όχι μόνο σε πρότυπα — αλλά η απόρριψη ισχύει μόνο στην παρουσίαση προτύπου.
5. Κάθε κλάδοςif constexprέχει το δικό του πεδίο εφαρμογής — οι μεταβλητές που δηλώνονται σε έναν κλάδο δεν είναι ορατές στον άλλο.

Constexpr Containers (C++20)

Το C++20 επιτρέπει τη χρήση τωνstd::vectorκαιstd::stringσε περιβάλλοντα constexpr. Μπορείτε να δημιουργήσετε, να χειριστείτε και να υποβάλετε ερωτήματα σε κοντέινερ τη στιγμή της μεταγλώττισης — μια τεράστια επέκταση των δυνατοτήτων προγραμματισμού χρόνου μεταγλώττισης. Ο περιορισμός: παροδική κατανομή — η μνήμη που εκχωρείται κατά την αξιολόγηση του χρόνου μεταγλώττισης πρέπει να ελευθερωθεί πριν τελειώσει η αξιολόγηση constexpr.

constexpr_containers.cpp
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
#include <array>

// Build and process a vector at compile time (C++20)
constexpr int sum_of_squares(int n) {
    std::vector<int> v;
    for (int i = 1; i <= n; ++i) {
        v.push_back(i * i);
    }
    return std::accumulate(v.begin(), v.end(), 0);
    // vector memory freed here — transient allocation
}

// Compile-time string processing
constexpr std::size_t count_vowels(std::string_view sv) {
    std::size_t count = 0;
    for (char c : sv) {
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' ||
            c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') {
            ++count;
        }
    }
    return count;
}

// Generate a lookup table at compile time
template <std::size_t N>
constexpr auto generate_squares() {
    std::array<int, N> result{};
    for (std::size_t i = 0; i < N; ++i) {
        result[i] = static_cast<int>(i * i);
    }
    return result;
}

static_assert(sum_of_squares(5) == 55); // 1+4+9+16+25
static_assert(count_vowels("Hello World") == 3);

constexpr auto squares = generate_squares<10>();
static_assert(squares[7] == 49);

Πρακτικός προγραμματισμός μεταγλώττισης-χρόνου

Ο υπολογισμός του χρόνου μεταγλώττισης δεν είναι απλώς ένα τέχνασμα. Οι πραγματικές χρήσεις περιλαμβάνουν: δημιουργία πίνακων αναζήτησης ενσωματωμένων στο δυαδικό, επικύρωση διαμορφώσεων κατά το χρόνο μεταγλώττισης, υπολογισμός κατακερματισμών για αντιστοίχιση συμβολοσειρών μεταγλώττισης και δημιουργία βελτιστοποιημένων διαδρομών κώδικα.

practical_constexpr.cpp
#include <array>
#include <cstdint>
#include <stdexcept>

// Compile-time CRC32 table generation
constexpr auto generate_crc32_table() {
    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 auto crc32_table = generate_crc32_table();

// Compile-time configuration validation
struct Config {
    int port;
    int max_connections;
    int timeout_ms;
};

consteval Config validated_config(int port, int max_conn, int timeout) {
    if (port < 1 || port > 65535)
        throw "Port out of range";  // compile error if invalid!
    if (max_conn < 1 || max_conn > 10000)
        throw "Invalid max connections";
    if (timeout < 0)
        throw "Negative timeout";
    return {port, max_conn, timeout};
}

// This validates at compile time — typo or bad value = build fails
constexpr auto config = validated_config(8080, 1000, 5000); // OK
// constexpr auto bad = validated_config(99999, 1000, 5000); // ERROR!
Παγίδα

Συνήθη λάθη με τον προγραμματισμό μεταγλώττισης:

1. Αν υποθέσουμε ότι τοconstexprσημαίνει μόνο χρόνο μεταγλώττισης — μια συνάρτησηconstexprμπορεί επίσης να εκτελεστεί κατά το χρόνο εκτέλεσης. Μόνο τοconstevalεγγυάται αξιολόγηση χρόνου μεταγλώττισης.
2. Μεταβατική κατανομή — σε περιβάλλοντα C++20 constexpr, όλη η μνήμη που εκχωρείται κατά την αξιολόγηση πρέπει να ελευθερωθεί πριν ολοκληρωθεί η αξιολόγηση. Δεν μπορείτε να αποθηκεύσετε έναconstexpr std::vectorως καθολικό (η μνήμη του σωρού θα παραμείνει).
3. Δεν είναι όλες οι συναρτήσεις constexpr-safe — I/O, δημιουργία νήματος,reinterpret_castκαι ενσωματωμένη συναρμολόγηση δεν μπορούν να εμφανιστούν σε περιβάλλοντα constexpr.
4. Υπερβολική χρήση του constexpr — ο υπολογισμός των πάντων στο χρόνο μεταγλώττισης αυξάνει τους χρόνους κατασκευής. Κρατήστε το για τιμές που ωφελούνται πραγματικά από το ότι είναι σταθερές χρόνου μεταγλώττισης.

Βασικά συμπεράσματα
  • Οι συναρτήσειςconstexprεκτελούνται κατά το χρόνο μεταγλώττισης Ή το χρόνο εκτέλεσης ανάλογα με το περιβάλλον
  • Τοconsteval(C++20) επιβάλλει την αξιολόγηση μόνο μεταγλώττισης - τα ορίσματα χρόνου εκτέλεσης είναι σφάλματα μεταγλώττισης
  • Τοconstinit(C++20) διασφαλίζει την προετοιμασία μεταγλώττισης στατικών/τοπικών μεταβλητών σε χρόνο μεταγλώττισης, αλλά επιτρέπει την τροποποίηση χρόνου εκτέλεσης
  • Τοif constexprαπορρίπτει τον κλάδο που δεν έχει ληφθεί, ο οποίος δεν χρειάζεται να είναι έγκυρος για τα δεδομένα ορίσματα προτύπου
  • Το C++20 επιτρέπει ταstd::vectorκαιstd::stringσε περιβάλλοντα constexpr με μεταβατική κατανομή
  • Οι πρακτικές χρήσεις περιλαμβάνουν τη δημιουργία πινάκων αναζήτησης, την επικύρωση διαμόρφωσης και τον κατακερματισμό στο χρόνο μεταγλώττισης

Κουίζ — Δοκιμάστε τις γνώσεις σας

(15 XP)

1. Ποια είναι η διαφορά μεταξύ`constexpr`και`consteval`;

2. Μπορείτε να έχετε μια`constexpr std::vector<int>`ως καθολική μεταβλητή στο C++20;

3. Τι εγγυάται το`constinit`;