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.
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:
__cpp_explicit_this_parameter,
__cpp_if_consteval, __cpp_constexpr stb.) a
várt vagy annál nagyobb értékkel;<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;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.
| 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.
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.
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.
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_result20A 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.
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.
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.
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.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.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.