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.
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 |
^^, std::meta és a spliceA 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.cppMié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.
pre,
post és contract_assertA 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).
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.
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.
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?
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.
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.