concept in generic programming (tsz. concept in generic programmings)
A generikus programozás célja, hogy általános algoritmusokat és adatstruktúrákat írjunk, amelyek különböző típusokkal is működnek. Ennek eszköze a template mechanizmus (pl. C++-ban template
kulcsszóval), amely lehetővé teszi, hogy típust paraméterként adjunk át egy osztálynak vagy függvénynek.
De felmerül egy fontos kérdés:
👉 Mit várunk el az átadott típustól?
Ha például egy sort
algoritmust írunk, az elemeket össze kell tudni hasonlítani. Ha egy sum
algoritmust írunk, az elemeket össze kell tudni adni. Ezek az elvárások nem mindig látszanak a template definícióban — és ha a felhasználó “rossz” típust ad át, a hiba csak bonyolult fordítási hibák formájában jelenik meg.
A megoldás erre a problémára a concept fogalma.
Egy concept a generikus programozásban egy leírás arról, hogy egy típusnak milyen műveleteket kell támogatnia, és ezeknek a műveleteknek milyen szintaxisa és szemantikája van.
Másképpen fogalmazva: egy concept szerződés (contract), ami előírja, hogy a típusnak mit kell tudnia ahhoz, hogy egy adott algoritmusban vagy osztályban használható legyen.
Sortable
concept kimondhatja, hogy egy típusnál léteznie kell <
, <=
, >
, >=
, ==
, !=
műveleteknek.Addable
concept kimondhatja, hogy léteznie kell +
operátornak két példány között.Range
concept kimondhatja, hogy a típusnak támogatnia kell a begin()
és end()
metódusokat, hogy iterálható legyen.
A concept hasonlít egy absztrakt típushoz (mint pl. egy interface Java-ban vagy egy abstract class C++-ban), de van egy fontos különbség:
👉 A concept nem követel meg öröklődési (subtype) kapcsolatot.
Más szóval: nem kell, hogy a típus explicit módon “implementálja” a concept-et (pl. nem kell örökölnie valamilyen bázisosztályból). Elég, ha a típus illeszkedik a concept-re, azaz támogatja a megfelelő műveleteket a kívánt módon.
Ezt a technikát hívják duck typing-nek is:
“If it looks like a duck, swims like a duck, and quacks like a duck — then it is a duck.”
Ha egy típus úgy viselkedik, mint amit a concept elvár, akkor használható is az adott algoritmusban.
A C++20 szabvány óta a concept kulcsszóval explicit módon lehet concept-et definiálni.
Példa:
template <typename T>
concept Incrementable = requires(T x) {
{ ++x } -> std::same_as<T&>;
{ x++ } -> std::same_as<T>;
};
Mit jelent ez?
T
típus példányán lehessen prefix és postfix inkrementálást végezni (++x
és x++
).Egy algoritmus így már használhatja ezt a concept-et:
template <Incrementable T>
void incrementAll(std::vector<T>& vec) {
for (auto& x : vec) {
++x;
}
}
Ha a felhasználó olyan típust ad át, ami nem Incrementable, a fordító érthető hibaüzenetet ad.
A concept-ek leírják, hogy a template paraméternek mit kell tudnia. Ez olvashatóbbá és karbantarthatóbbá teszi a kódot.
Concept nélkül a template hibák gyakran rejtélyesek:
template <typename T>
T add(T a, T b) {
return a + b;
}
add("hello", 42); // ??? Fordítási hiba, de miért?
Concept használatával:
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b };
};
template <Addable T>
T add(T a, T b) {
return a + b;
}
Most a fordító világosan jelzi, ha a típus nem felel meg az Addable
concept-nek.
Concept-ekkel a fordító jobb kódot tud generálni, mert pontosabban tudja, milyen műveletek lesznek elérhetők.
A concept-ek segítségével nagyon általános algoritmusokat lehet írni úgy, hogy közben explicit módon megmondjuk, milyen típusok használhatók.
Példa:
template <typename T>
concept EqualityComparable = requires(T a, T b) {
{ a == b } -> std::convertible_to<bool>;
{ a != b } -> std::convertible_to<bool>;
};
template <EqualityComparable T>
bool allEqual(const std::vector<T>& vec) {
if (vec.empty()) return true;
const T& first = vec.front();
for (const auto& item : vec) {
if (!(item == first)) return false;
}
return true;
}
Fogalom | Követel öröklődést? | Ellenőrzi a szintaxist? | Ellenőrzi a szemantikát? |
---|---|---|---|
Abstract class | Igen | Igen | Igen |
Interface | Igen | Igen | Igen |
Concept | Nem | Igen (compile time) | Részben (compile time) |
Duck typing (dynamic, pl. Python) | Nem | Nem (runtime hiba) | Nem (runtime hiba) |
A concept statikus ellenőrzést biztosít (fordítási időben), de nem ír elő explicit öröklődést. Ez nagy előnye a generikus programozásban.
A C++20 előtti világban sokan SFINAE-t (Substitution Failure Is Not An Error) használtak template kódban a concept-ekhez hasonló viselkedés elérésére.
Példa:
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
void foo(T x) { ... }
Ez ugyan hatékony, de:
A C++20 concept mechanizmusa szabványos, tiszta és olvasható alternatíva.
concept
kulcsszó).