46.1 C++23 összefoglaló és migrációs táblázat

A kurzus alapja a C++20, a C++23-as elemeket pedig végig külön jelöléssel, esetenként fallbackkel használtuk. Ez az alfejezet abból a szempontból zárja le a C++ részt, hogy a szétszórt jelöléseket egyetlen migrációs leltárba rendezi: milyen képesség, melyik a minimumstandard, mit kell a laborprofilban ellenőrizni, és mi a visszaút, ha a kiválasztott fordító vagy standard library nem tudja. Ez nem újratanítás: a szemantikát a Záróreview és C/C++ összehasonlítás már rögzítette, a mérési és feature-detection technikát pedig a Feature-test, standard library és build capability írta le. Itt ezekre építünk.

46.1.1 A migráció alapszabálya: képesség nem egyenlő elérhetőséggel

A migráció legfontosabb fogalmi különbsége, hogy a szabványos képesség (capability) és a támogatottság (availability) két külön dolog. A szabvány azt mondja meg, hogy egy kifejezés, típus vagy fejléc milyen szemantikával létezik; az implementáció azt mondja meg, hogy a te konkrét fordító-, illetve libstdc++/libc++ kiadásodban ez tényleg ott van-e. A kettő közé nem lehet egyenlőségjelet tenni, és nem lehet egyikből a másikra következtetni.

Egy C++23 képességet a laborprofilban akkor tekintünk elérhetőnek, ha mindhárom feltétel egyszerre teljesül:

  1. a fordító előre definiálja a megfelelő nyelvi feature-test makrót (__cpp_explicit_this_parameter, __cpp_if_consteval, __cpp_constexpr stb.) a várt vagy annál nagyobb értékkel;
  2. a standard library a megfelelő fejlécet szolgáltatja, és a <version>-ból (vagy a fejlécből) olvasható library makró (__cpp_lib_expected, __cpp_lib_print, __cpp_lib_mdspan, __cpp_lib_ranges_to_container stb.) definiált;
  3. a kód lefordul, és a viselkedés megfelel a szerződésnek — nem csak a deklaráció látszik.

Ha bármelyik hiányzik, akkor a képesség a te profilodban nem használható, függetlenül attól, hogy a szabvány szerint létezik. A -std=c++23 kapcsoló önmagában csak a nyelvi módot kéri; nem garantálja, hogy a library is követte. A fordítók egy része a módot régebbi nevén (-std=c++2b) is elfogadja, ezt a laborprofilban rögzíteni kell, mert két különböző build ugyanazt a forrást másképp láthatja.

A táblázatban a makróértékek emlékeztető jellegűek: ezek verziófüggő részletek, ezért a laborban a tényleges <version> fejlécből vagy a rögzített fordító dokumentációjából kell megerősíteni őket. A „Profil” oszlop szándékosan üres: azt a laborprofilodban kell kitölteni a konkrét GCC/Clang és library kiadással.

46.1.2 Capability tábla a kurzus C++23 példáihoz

Képesség Minimum Feature-test makró Fallback C++20-hoz
explicit object parameter (deducing this) C++23 __cpp_explicit_this_parameter &/&& minősített overload párok
if consteval C++23 __cpp_if_consteval std::is_constant_evaluated()
constexpr bővítések C++23 __cpp_constexpr a constexpr függvény runtime-ra korlátozása
std::expected C++23 __cpp_lib_expected saját Result<T,E> std::variant-tal
std::print / std::println C++23 __cpp_lib_print std::format + kiírás, vagy std::cout
std::mdspan C++23 __cpp_lib_mdspan kézi indexszámítás, saját view struct
std::generator C++23 __cpp_lib_generator callback-alapú iteráció
std::move_only_function C++23 __cpp_lib_move_only_function std::function, ha másolható az állapot
Ranges bővítések (zip, chunk, slide, join_with, stride, cartesian_product) C++23 __cpp_lib_ranges_* család kézi indexelés, transform + copy
ranges::to, from_range konstruktorok C++23 __cpp_lib_ranges_to_container, __cpp_lib_containers_ranges std::vector(v.begin(), v.end())
std::byteswap C++23 __cpp_lib_byteswap kézi shift/maszk
std::to_underlying C++23 __cpp_lib_to_underlying static_cast<std::underlying_type_t<E>>(e)
std::unreachable C++23 __cpp_lib_unreachable assert(false) + fordítóbővítmény
std::stacktrace C++23 __cpp_lib_stacktrace backtrace()/backtrace_symbols()

A táblázat nem teljes körű C++23-leltár, hanem a könyvben ténylegesen előforduló elemek inventoryja. A hiányzó C++23-as képességek (például a formázási bővítések egy része) migrációs szempontból ugyanezt a mintát követik: makró, fejléc, fallback.

46.1.3 Nyelvi képességek

A deducing this (explicit object parameter) a tagfüggvény implicit this paraméterét teszi láthatóvá és template-paraméterré. A feltétel, hogy a paraméterlista első eleme this előtagú legyen; ekkor a függvény nem kap külön implicit objektumparamétert. Az eredmény az, hogy ugyanaz a függvénytörzs szolgálja ki az lvalue és rvalue hívást, a value category a Self-en keresztül megőrizve:

struct Builder {
    std::string name;
    template <class Self>
    auto&& with_name(this Self&& self, std::string n) {
        self.name = std::move(n);
        return std::forward<Self>(self);   // lvalue -> Builder&, rvalue -> Builder&&
    }
};

A C++20-as fallback két külön overload: Builder& with_name(std::string) & és Builder&& with_name(std::string) &&, azonos törzzsel. A hívási oldal mindkét esetben Builder{}.with_name("x"), illetve b.with_name("x").

Az if consteval a constexpr függvényen belül ágat nyit aszerint, hogy a kiértékelés constant evaluation alatt történik-e. Feltétele, hogy a függvény constexpr legyen; a hamis ág csak futásidőben értékelődik. A C++20-as megfelelője a std::is_constant_evaluated(), amely ugyanazt a döntést hozza, de nem ad külön blokkot, így a nem-constexpr műveleteket kézzel kell elkerülni. A constexpr bővítések (például static és thread_local változók constexpr függvényben) ugyanebbe a családba tartoznak: a makró értékét ellenőrizni kell, mert a bővítések nem egyetlen kapcsolóhoz kötődnek.

A std::unreachable() azt fejezi ki, hogy az adott pontot a program soha nem érheti el. Ez nem egy „ez úgyis ritka” jelölés, hanem szerződés: ha mégis elérhető, a viselkedés nem definiált, és azoptimalizáló erre építve tetszőleges kódot generálhat. Helyes használat például egy teljes switch után, ahol minden enumerátort lefedtünk. Fallbackként assert(false) használható, ami legalább a debug buildben megáll.

46.1.4 Könyvtári képességek

A std::expected<T,E> egy value-or-error típus: vagy T-t, vagy E-t tartalmaz, soha nem mindkettőt, és nem allokál. Feltétele, hogy T és E destruktálható legyen, és a két típus ne legyen ugyanaz. C++23-ban monadikus műveletei is vannak (.and_then, .transform, .or_else). A std::print/std::println a <print> fejlécben él, és a std::format szemantikáját használja a streams állapotgép helyett. A std::mdspan többdimenziós, nem tulajdonló nézet kontinuus tárolóra, saját extents és layout policy szerződéssel; a C++23-as többargumentumos operator[] a kulcs. A std::generator coroutine-alapú, lusta sorozat, amelyet co_yield-tel táplálunk. A std::move_only_function type-erasure wrapper, amelynek callable-je nem másolható — ott kell, ahol a std::function másolási követelménye miatt nem lehetne unique_ptr-t capture-elni.

A Ranges bővítések (views::zip, chunk, slide, join_with, stride, cartesian_product) a pipeline-ba illeszkedő, lusta nézetek; a ranges::to<C> és a from_range_t konténer-konstruktorok a range-ből konténerbe konvertálás hivatalos útját adják. A std::byteswap endian-fordítást végez, a std::to_underlying enumról a mögöttes egész típusra konvertál, a std::stacktrace pedig szimbolizált hívási láncot ad — utóbbi erősen implementációfüggő, és a szimbolizációhoz gyakran külső library kell.

46.1.5 Kidolgozott példa: capability probe fallbackkel

Az alábbi példa a migráció tipikus alakját mutatja: egy vékony aliastípus, amely a makrótól függően a szabványos vagy a fallback implementációra képződik. A konstrukciót szándékosan egységesre vettük, hogy a hívási oldal ne változzon.

// probe_result.cpp
#include <version>
#include <charconv>
#include <string>
#include <string_view>
#include <variant>

#if defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L
#include <expected>
template <class T, class E> using Result = std::expected<T, E>;
inline constexpr const char* kBackend = "std::expected";
#else
template <class T, class E>
struct Result {
    std::variant<T, E> v;
    bool has_value() const { return v.index() == 0; }
    T& value() { return std::get<0>(v); }
    E& error() { return std::get<1>(v); }
};
inline constexpr const char* kBackend = "fallback/variant";
#endif

Result<int, std::string> parse_int(std::string_view s) {
    int value{};
    auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value);
    if (ec == std::errc{} && ptr == s.data() + s.size())
        return Result<int, std::string>{value};            // value ág
    return Result<int, std::string>{std::string{"nem egesz: "} + std::string{s}}; // error ág
}
# C++23 mód, a szabványos ág remélt útja:
g++ -std=c++23 -Wall -Wextra -Wpedantic -O2 probe_result.cpp -o probe_result

# Ugyanaz a forrás C++20 módban: itt a fallback ágnak kell fordulnia.
g++ -std=c++20 -Wall -Wextra -Wpedantic -O2 probe_result.cpp -o probe_result20

A példa korlátját érdemes kimondani: ez a shim nem teljes expected-helyettesítő, monadikus műveletek és void-specializáció nélkül. A célja az, hogy a migrációt egyetlen fordítási ponton tartsa. A fordítást a saját laborprofilodban kell elvégezni; a szöveg nem állít mért eredményt, és a kBackend értéke a te implementációdtól függ.

46.1.6 Tipikus hibák

A leggyakoribb hiba, hogy a build -std=c++20-szal zöld marad, mert a fallback ág fordul — a migráció csendben nem történt meg. Ez ellen build-time őr kell: static_assert(__cpp_lib_expected), ha a szabványos ág kötelező. A második, hogy a nyelvi és a library makrót összekeverik: a fordító ismerheti a szintaxist, miközben a régi libstdc++ még nem adja a fejlécet. A harmadik a std::unreachable() visszaélése olyan értékre, amely előfordulhat — ez nem definiált viselkedés, nem „gyorsítás”. A negyedik, hogy a <version>-t nem include-olják, és a makró „hiányzik”; a library makrókat a megfelelő fejlécből vagy a <version>-ból kell olvasni, de a kettő nem mindig ekvivalens.

46.1.7 Összefoglaló

A C++23-migráció nem egyetlen kapcsoló, hanem képességenkénti döntés. Minden képességhez három dolgot kell rögzíteni: a minimumstandardot, a tényleges laborprofilbeli elérhetőséget (nyelvi és library makróval igazolva) és a fallbacket. A táblázat ezt a leltárt adja; a Result<T,E> példa pedig azt mutatja meg, hogyan tartható a hívási oldal változatlanul. A C++26 irányai külön fejezetben, külön ellenőrzéssel szerepelnek — itt szándékosan nem garantálunk semmit arról, ami még nem stabil.

46.1.8 Feladatok

  1. Rendszerezés. Válogasd szét a fenti táblázat sorait nyelvi és könyvtári képességekre, és indokold, melyiknél nagyobb a migrációs kockázat, ha csak a fordítót frissíted, de a standard libraryt nem.
  2. Kódolás. Egészítsd ki a Result<T,E> példát egy unwrap_or(T fallback) tagfüggvénnyel mindkét ágon, és add meg a buildparancsot. Készíts olyan őrt, amely C++20 módban fordítási hibát ad, ha a fallback ág véletlenül a szabványos ágra képződne.
  3. Diagnosztika. Egy CI-ban ugyanaz a forrás C++20-ban zöld, C++23-ban piros. Sorolj fel három lehetséges okot, és add meg, milyen makrókkal és parancsokkal szűkítenéd a hibát.
  4. Élettartam. A std::mdspan és a std::string_view közös tulajdonsága, hogy nem tulajdonló nézetek. Fogalmazz meg egy szabályt arra, mikor biztonságos egy mdspan-t függvényből visszaadni.

46.1.9 Megoldások

1. Nyelvi képesség a deducing this, az if consteval, a constexpr bővítések és a std::unreachable szemantikai magja; könyvtári az expected, print, mdspan, generator, move_only_function, a Ranges bővítések, a byteswap, to_underlying és a stacktrace. A kockázat a könyvtári oldalon nagyobb: a nyelvi makró a fordítóval együtt jön, a library makró viszont attól függ, hogy a rendszeren melyik libstdc++/libc++ van linkelve — és konténerben ez gyakran nem az, amit a fordítóhoz vártunk. Ellenőrzés: a két build -v kimenete és a <version> tartalma alapján a profil egyezzen.

2. A fallback ágon T unwrap_or(T fb) { return has_value() ? value() : std::move(fb); }, az expected ágon T unwrap_or(T fb) { return has_value() ? *this : std::move(fb); }. A közös hívási oldal miatt mindkettőnek ugyanaz a szignatúrája. Az őr: #if defined(NDEBUG) ... #else static_assert(!std::is_same_v<Result<int,std::string>, std::expected<int,std::string>>); a fallback ágban — ez akkor hibázik, ha a makrólogika fordítva működik. Ellenőrzési feltétel: a C++20-as buildben a static_assert nem fut le, a C++23-asban a szabványos ág aktív.

3. (a) A -std=c++23 mellett a fordító új szintaxist fogad el, de a régi libstdc++ nem adja a fejlécet → hiányzó include. (b) A kód olyan konstrukciót használ, amely C++20-ban mást jelentett (például megváltozott overload-feloldás), és a régi build ezt nem fedte fel. (c) Egy harmadik féltől származó fejléc a saját expected-jét definiálja, ami ütközik. Szűkítés: -E -dM a makrókhoz, -H az include-lánchoz, -fdiagnostics-show-template-tree a template-hibákhoz, és két külön build könyvtár, hogy a régi object fájlok ne maszkolják a hibát.