45.1 C++26 nyelvi kitekintés: reflexió, contracts és kisebb újdonságok

Ez a fejezet szándékosan kitekintés, nem tananyagmélységű tárgyalás. A C++26 nyelvi képességeit azért érdemes korán megismerni, mert több közülük olyan mintákat vált ki, amelyeket a könyv korábbi részeiben C-vel és C++20/23-mal kézzel építettünk fel: a makrós „reflexiót”, a kézzel írt elő- és utófeltétel-ellenőrzéseket, a bináris erőforrások beágyazását és a static_assert üzeneteinek kézi összefűzését. A cél tehát nem az, hogy most rögtön termelési kódot írjunk C++26-ban, hanem hogy felismerjük: melyik korábbi szerződésünket fogja a nyelv vagy a könyvtár átvenni, és mi marad továbbra is a mérnök feladata.

A státuszállítások forrása a forrásjegyzék [C++26] tétele, 2026-10-25-i ellenőrzéssel: a C++26 technikai munkája 2026. március 28-án lezárult, a szabvány DIS-szavazáson van, ISO-kiadását az ellenőrzés napján nem találtuk igazolva. A fejezetben tárgyalt képességek (statikus reflexió, P2996R13; contracts, P2900R14; #embed, P1967R14; pack indexing, P2662R3; a _ helyőrző, P2169R4; strukturált kötés csomagja, P1061R10; = delete("ok"), P2573R2; számított static_assert-üzenet, P2741R3; erroneous behaviour, P2795R5) a lezárt C++26 részei; a papírlistát a cppreference C++26-oldalának ugyanazon napi állapotából vettük (másodlagos forrás). Az implementációs támogatást a könyv csak a saját profiljain méri: a lab-gcc profil GCC 13.3.0 fordítója egyiket sem támogatja, -std=c++2c módja sincs. A fejezet kódpéldáit ezért nem fordítottuk le; ahol egy állítás verziófüggő, ott ezt jelölöm.

45.1.1 A státuszolvasás: elfogadott, kiadott, implementált

A C++ szabványosításban három, egymástól élesen elkülönítendő állapot van, és a gyakorlati hibák többsége ennek a háromnak az összemosásából származik.

Elfogadott (adopted). Egy papírt a WG21 munkacsoport a munkatervezetbe (working draft) fogadott. Ettől a képesség „bent van” a következő szabvány tervezetében, de ez nem jelenti azt, hogy bármelyik fordító ismeri.

Kiadott (published). Az ISO megjelentette a szabvány végleges szövegét. A fejezetterv kiinduló adata szerint a C++26 nyelvi jellemzői 2025-ben befagytak (feature complete), a kiadás pedig ezt követően várható. Ez a dátum és állapot ellenőrizendő a hivatalos ISO/WG21 közleményekből; a fejezet nem állítja, hogy a kiadás megtörtént.

Implementált (implemented). Egy adott fordító- és standard library-kiadás ténylegesen fordítja és helyesen viselkedik. Ez a szint a legfontosabb a mindennapi munkában, és ez a leglassabb. Egy képességhez ezért három dolgot kell megnézni: a papír státuszát, a __cpp_... feature-test makró jelenlétét az adott implementációban, és a konkrét compiler/library verziót.

A fejezet képességeinek státusza ebben a könyvben egységesen: kísérleti, nem része a laborprofilnak. A laborok továbbra is C++20-at (-std=c++20) használnak, a C++23-as részek pedig külön jelölt profillal futnak.

Képesség Papír Állapot ebben a fejezetben
statikus reflexió P2996 kísérleti, csak szemléltetés
contracts P2900 kísérleti, csak szemléltetés
pack indexing P2662 kitekintés
_ placeholder P2169 kitekintés
#embed P1967 kitekintés (C23-ban is létezik)
erroneous behaviour P2795 szemantikai kitekintés

45.1.2 Statikus reflexió: ^^, std::meta és a splice

A statikus reflexió (static reflection) lényege, hogy a program fordítási időben tudjon kérdezni a saját típusairól, tagjairól, enumerátorairól, és ezekre hivatkozni tudjon. A P2996 mechanizmusa két fogalomra épül.

A ^^ operátor (reflection operator) egy std::meta::info típusú értéket állít elő: ez egy átlátszatlan, fordítási idejű fogantyú egy entitásra (típusra, névtérre, tagra). A lekérdezések a std::meta névtér consteval függvényei: például nonstatic_data_members_of, identifier_of, type_of, parent_of. A splice a másik irány: a [: ... :] szintaxis egy std::meta::info alapján visszahelyettesít egy entitást a forrásba — típusnév, tag, kifejezés vagy sablonargumentum helyére.

// Illusztráció a P2996 papír szerinti alakból; kísérleti fordítóval sem ellenőriztük.
#include <meta>
#include <string_view>

struct listener {
    int port;
    std::string_view bind_address;
};

consteval std::size_t field_count(std::meta::info type) {
    return std::meta::nonstatic_data_members_of(
               type, std::meta::access_context::current())
        .size();
}

static_assert(field_count(^^listener) == 2);

template<std::meta::info Member>
constexpr auto& field(auto& obj) {
    return obj.[:Member:];   // splice: a tag neve helyére
}

A kód buildparancsa a stabil részhez (a field_count és a static_assert nélkül) a szokásos profil; a teljes példa fordítása kísérleti fordítóágat igényel, amelynek kapcsolóit a kiadott dokumentációból kell ellenőrizni:

# A kísérleti profil kiinduló parancsa; a flagek verziófüggők, ellenőrizendők.
# g++ --version && g++ --help=common | grep -i reflect
g++ -std=c++26 -fsyntax-only reflection_demo.cpp

Miért fontos ez a könyv szempontjából? A Makrók, feltételes fordítás és veszélyes helyettesítés részben (2.3, „X-macro: egyetlen taglista több helyen”) az X-macro technikát használtuk arra, hogy egy struct tagjait egyetlen listából deklaráljuk, megszámoljuk és név szerint kiírjuk. Az X-macro helyes, de törékeny: a táblát kézzel kell szinkronban tartani a típussal. A reflexió ezt a kézi szinkront váltja ki: a tagok listája a típusból származik, így nem lehet elcsúszni. Ugyanez igaz a Layout- és protokolllabor diagnosztikájára: az offsetek és nevek generálhatók, nem kézzel írandók.

45.1.3 Contracts: pre, post és contract_assert

A P2900 contracts a függvény szerződését viszi be a szintaxisba. A pre(cond) előfeltétel a paraméterlista után, a post(cond) utófeltétel a törzs előtt áll; az utófeltételben névvel hivatkozhatunk a visszatérési értékre. A contract_assert(cond) egy utasítás, amely a törzsön belüli belső invariánst ellenőrzi.

// Illusztráció a P2900 papír szerinti alakból; kísérleti, nem tesztelt.
#include <cstddef>

int clamp_index(int i, std::size_t n)
    pre(i >= 0)
    pre(static_cast<std::size_t>(i) < n)
    post(r: r >= 0 && static_cast<std::size_t>(r) < n)
{
    contract_assert(n > 0);
    return i;
}

A lényeg a kiértékelési szemantika, mert ez különbözteti meg az assert-nél megszokott mindent-vagy-semmit modelltől. A papír négy üzemmódot ír le: ignore (a feltétel nem értékelődik ki), observe (kiértékelődik, megsértés esetén a violation handler lefut, de a program folytatódik), enforce (kiértékelődik, megsértés esetén a handler lefut, majd a program leáll), quick-enforce (kiértékelődik, handler nélkül azonnal leáll). A módot fordításidejű beállítás választja, tehát ugyanaz a forrás kerül a fejlesztői és a termelési buildbe — ez a contracts legfontosabb ígérete.

Ez a korábbi anyagban két helyet érint. Egyrészt az Unit test, property test és fuzzing hibafelfedező rétegét: az előfeltételek most a fejlesztői buildben automatikusan aktívak, a release buildből pedig eltávolíthatók, anélkül hogy a forrás kettéválna. Másrészt a Hibakód, errno és hibaadatok élettartama hibamodelljét: a contracts programozói hibát jelez, nem futásidejű, várható hibát. Egy hiányzó fájl, egy megszakadt kapcsolat nem szerződéssértés, hanem normál hibaút, amelyre továbbra is std::expected vagy hibakód a válasz (std::expected mélyebben).

45.1.4 Kisebb nyelvi újdonságok

Pack indexing (P2662). Sablon-paramétercsomag eddig csak expandálható volt; most közvetlenül indexelhető: Ts...[0], Ts...[I]. Ez kiváltja a rekurzív „vesd el az elsőt” segédsablonokat, és tipikusan olyan helyen hasznos, ahol eddig std::tuple_element-hez kellett menekülni.

template<class... Ts>
using first_t = Ts...[0];

template<std::size_t I, class... Ts>
using nth_t = Ts...[I];

A _ placeholder (P2169). A _ újra deklarálható helyőrző név, amely szándékosan „nem nevez meg” semmit: nem ad rá figyelmeztetést a -Wunused, és ugyanabban a hatókörben többször is szerepelhet. A for (auto _ : xs) és a strukturált kötés tipikus felhasználási esete.

Strukturált kötés bővítései. A P1061 lehetővé teszi, hogy a strukturált kötés csomagot vezessen be (a papír szintaxisa szerint auto [head, ...tail] = t; alakban), ami a rekurzív tuple-feldolgozást váltja ki. Emellett a konstans kiértékelés oldalán is bővült a nyelv: a constexpr strukturált kötések, a void*-ból végzett constexpr cast és a constexpr placement new a C++26 papírjai között szerepelnek — a teljes lista ebben a kitekintésben nem teljes.

= delete("ok") és a static_assert üzenete. A törölt függvény indoklása most szövegesen is megadható: fd(int) = delete("fd nem építhető nyers int-ből"); — a diagnosztika a felhasználó szövegét is kiírja. A static_assert üzenete pedig lehet konstans kiértékelésű kifejezés eredménye, így a diagnosztika számított adatot (méretet, típusnevet) tartalmazhat. Ez közvetlenül a Konstans kiértékelés C++23-ban és a constexpr, consteval és constant evaluation tudására épül: az üzenetnek fordítási időben kiszámolhatónak kell lennie.

#embed (P1967). A preprocessor bináris fájl tartalmát ágyazza be, vesszővel elválasztott egész literálok listájaként (egy ABC tartalmú fájlból 65, 66, 67 lesz). A paraméterek: a limit(n) legfeljebb n bájtot olvas; a prefix(...) és a suffix(...) tokensorozata egyszer, a teljes lista elé, illetve mögé kerül, és csak akkor, ha a beágyazott tartalom nem üres; az if_empty(...) üres tartalom esetén lép a lista helyére. A prefix tehát nem elemenkénti formázás. A prefix(0x) a 0x tokent egyszer, az első szám elé teszi, és ebből egyetlen érvénytelen tokenszerű sorozat (0x65-höz hasonló) keletkezne; a fordító ezt elutasítja.

/* embed_demo.c — C23: #embed és paraméterei */
#include <stdio.h>

/* Egyszerű bájttömb: a #embed vesszővel elválasztott egész literálokat ad. */
static const unsigned char icon[] = {
#embed "three.bin" limit(64)
};

/* prefix/suffix: egyszer, a teljes lista elé/mögé kerül, ha a fájl nem üres;
   if_empty: üres fájl esetén ez kerül a helyére. */
static const unsigned char framed[] = {
#embed "one.bin" prefix(0xFF, ) suffix(, 0x00) if_empty(0xEE)
};

static const unsigned char none[] = {
#embed "empty.bin" prefix(0xFF, ) suffix(, 0x00) if_empty(0xEE)
};

int main(void) {
    printf("icon: %zu bájt, első=%d\n", sizeof icon, icon[0]);
    printf("framed:");
    for (size_t i = 0; i < sizeof framed; ++i) printf(" %d", framed[i]);
    printf("\nnone:");
    for (size_t i = 0; i < sizeof none; ++i) printf(" %d", none[i]);
    printf("\n");
    return 0;
}

Az előfeldolgozott initializerek: three.bin (ABC) esetén {65, 66, 67}; az egybájtos one.bin (A) prefixszel és suffixszel {0xFF, 65, 0x00}; az üres empty.bin esetén az if_empty miatt {0xEE}.

Buildjegyzőkönyv. A lab-gcc profil GCC 13.3-a nem támogatja az #embed-et. Ellenőrzésképpen a ziglang 0.17.0 pip-csomagban szállított Clang 22.1.8-cal fordítottuk, -std=c23 -Wall -Wextra -Wpedantic mellett (ez a lab-next profil része). A program figyelmeztetés nélkül fordult; kimenete icon: 3 bájt, első=65, framed: 255 65 0, none: 238, a -E kimenet a fenti initializereket mutatta. Az eredeti prefix(0x) suffix(,) alak invalid suffix 'x' on integer constant hibát adott.

Figyeljük meg a kereszthivatkozást: a #embed a C23-ban is megjelent (C23 könyvtár és preprocessor), tehát itt nem új C++-saját ötletről van szó, hanem a két szabvány közös preprocessor-képességéről. A C++ oldalon a std::embed-féle könyvtári változat nem része ennek a kitekintésnek.

Erroneous behaviour (P2795). A P2795 az inicializálatlan automatikus változó olvasását nem UB-ként, hanem „erroneous behaviour” kategóriával írja le. Ez nem teszi helyessé a kódot: a program továbbra is hibás és nem hordozható, viszont a szabvány ezzel lehetővé teszi, hogy az implementáció diagnosztizálja a hibát, és ne kelljen az egész program optimalizálását „bármi megtörténhet” alapon kezelnie. Ez pontosan a Warningok, optimalizálási profilok és link-driver és az Optimalizáló, as-if és UB következményei részben tárgyalt UB-fogalom finomítása — a helyes válasz változatlanul az inicializálás.

45.1.5 Tipikus hibák

A leggyakoribb hiba a státusz-összemosás: valaki a munkatervezetben látott szintaxist beírja a termelési kódba, majd a CI egy régebbi fordítón elhasal. A második: a contracts és a futásidejű hibakezelés összetévesztése — szerződéssértésre catch-csel reagálni értelmetlen, mert enforce módban a program leáll. A harmadik: a reflexiót futásidejű mechanizmusnak hinni; a std::meta lekérdezések consteval kontextusban élnek, futásidőben nem hívhatók. A negyedik: az #embed és a #include összekeverése — a #embed nem fejlécet, hanem bájttömb-literalokat illeszt be. Az ötödik: a prefix/suffix elemenkénti formázásnak hinni; mindkettő egyszer, a teljes lista körül jelenik meg.

45.1.6 Feladatok

1. feladat (státusz). Sorold be az alábbi állításokat „elfogadott / kiadott / implementált” kategóriába, és indokold: (a) „A statikus reflexió benne van a C++26 munkatervezetben.” (b) „A -std=c++26 kapcsolóval a GCC lefordítja a ^^T kifejezést.” (c) „Az ISO megjelentette a C++26-ot.”

2. feladat (kódolás). Írj egy consteval függvényt, amely egy struct nem statikus adattagjainak számát adja vissza, és ellenőrizd static_assert-tel. Készítsd el ugyanennek a C++20-as megfelelőjét X-macro-val, és fogalmazd meg, melyik változat milyen karbantartási hibát zár ki.

3. feladat (diagnosztika). Adott a clamp_index függvény. Írd le, mi történik observe, illetve enforce módban, ha n == 0 és i == 0 mellett hívjuk. Hol lép fel a hiba: a hívásnál, a belépésnél vagy a visszatérésnél?

4. feladat (tervezés). Egy bináris protokollverzió-tömböt szeretnél beágyazni. Hasonlítsd össze a #embed, a generált fejléc és a futásidejű fájlbeolvasás megoldását: melyik milyen build- és hordozhatósági szerződést igényel?

45.1.7 Megoldások

1. (a) elfogadott — a munkatervezetbe fogadás ténye, függetlenül a kiadástól és az implementációktól. (b) implementált — ez konkrét fordító és konkrét verzió viselkedése, amelyet feature-test makróval és a --version kimenetével kell rögzíteni. (c) kiadott — a 2026-10-25-i ellenőrzés szerint a C++26 technikailag lezárt és DIS-szavazáson van, de ISO-kiadását nem találtuk igazolva; az állítás tehát csak akkor igaz, ha egy hivatalos ISO-közlemény dátumszerűen alátámasztja. Ellenőrzési feltétel: mindhárom állításhoz nevezz meg egy-egy elsődleges forrástípust (papír, compiler-dokumentáció, ISO-közlemény), amely eldönti.

2. A consteval függvényre vonatkozó minősítő: változót nem lehet consteval-lal deklarálni (a variable cannot be declared 'consteval'). A helyes szerkezet egy consteval függvény, amelynek eredményét constexpr változóba vagy static_assert-be tesszük. A reflexiós változat:

template <class T>
consteval std::size_t field_count() {
    return std::meta::nonstatic_data_members_of(
               ^^T, std::meta::access_context::current()).size();
}

constexpr std::size_t n = field_count<Config>();
static_assert(n == 2);

Ugyanez a szétválasztás reflexió nélkül, C++20-ban, kézi listával:

// consteval_split.cpp — C++20: consteval függvény, constexpr eredmény
#include <cstddef>

struct Config {
    int port;
    const char* bind_address;
};

// A consteval FÜGGVÉNYRE vonatkozik: minden hívása konstans kiértékelés.
consteval std::size_t field_count_manual() { return 2; }   // kézi lista

constexpr std::size_t n = field_count_manual();   // eredmény constexpr változóban
static_assert(n == 2);
static_assert(field_count_manual() == 2);

int main() { return static_cast<int>(n) - 2; }

Az X-macro változatban a taglista egy #define FIELDS(X) X(int, port) X(std::string_view, bind_address) típusú makróban él, és a struct ugyanebből a makróból épül. A reflexiós változat akkor sem csúszik el, ha valaki új tagot vesz fel, viszont kísérleti fordítót igényel; az X-macro minden C++20-as fordítón működik, de a listát és a típust kézzel kell együtt tartani. Ellenőrzési feltétel: a reflexió nélküli minimálpélda a lab-gcc profilban figyelmeztetés nélkül fordul és 0 kóddal fut, a consteval változó pedig fordítási hiba; mindkettőt ellenőriztük. A reflexiós példát nem fordítottuk le: ehhez P2996-ot támogató fordító kell, amely a könyv profiljai között nem ellenőrzött.

3. n == 0 mellett a második előfeltétel (i < n) hamis. observe módban a feltétel kiértékelődik, a violation handler lefut, majd a vezérlés a függvény törzsére lép, ahol a contract_assert(n > 0) szintén megsérül — a viselkedés tehát továbbra is hibás, csak nem áll le a program azonnal. enforce módban a handler után a program leáll, még a törzs előtt; a hiba tehát belépéskor, nem a visszatérésnél jelentkezik. Ellenőrzési feltétel: a válasznak meg kell neveznie, hogy a hiba a hívó oldal szerződésszegése, nem a hívott függvény belső hibája.

4. A #embed a legegyszerűbb, de a fájlnak a fordításkor elérhetőnek kell lennie, és a méret a binárisba fordítási időben bekerül; a build ezáltal a forrásfájl mellett a binárisra is függ. A generált fejléc ugyanezt a függést hozza létre, csak egy generátor-lépéssel, viszont hordozható minden C++20-as fordítón. A futásidejű beolvasás a legerősebb hordozhatóságot adja, de új hibautat (hiányzó fájl, részleges olvasás) és indítási költséget visz a programba. Ellenőrzési feltétel: a döntéshez mindhárom esetben add meg, hogy mi történik, ha a bináris tartalma megváltozik, és hogyan derül ki a CI-ban.

45.1.8 Összefoglaló

A C++26 nyelvi kitekintésében négy dolgot érdemes megjegyezni. Először: a státuszt mindig három szinten kell olvasni (elfogadott, kiadott, implementált), és a kísérleti képességek nem kerülnek be a laborprofilba. Másodszor: a statikus reflexió (^^, std::meta, splice) a makrós, kézzel szinkronban tartott mintákat váltja ki — ez a legnagyobb hatású változás a rendszerszintű kódra. Harmadszor: a contracts a szerződésszegést (programozói hibát) választja el a futásidejű hibakezeléstől, és a kiértékelési módot a build választja meg, nem a forrás. Negyedszer: a kisebb újdonságok (pack indexing, _, strukturált kötés csomagja, = delete("ok"), számított static_assert-üzenet, #embed, erroneous behaviour) egyenként aprók, együtt viszont érezhetően csökkentik a kézi és a törékeny kódot. A C++26 utáni irányokat és a részletes státusztáblát a C++26 és későbbi irányok ellenőrzött státusszal rész adja; a könyvtári és konkurencia-oldalt a C++26 könyvtári és konkurencia-kitekintés tárgyalja.