4 SFINAE, koncepty a type erasure
- omezit šablonu pomocí
requiresa poznat staršíenable_if - porovnat statický a dynamický polymorfizmus
- vysvětlit princip type erasure
- použít CRTP
4.1 Ukázka: omezení šablony
Chceme funkci jen pro čísla s plovoucí řádovou čárkou. Moderní zápis je koncept:
#include <cassert>
#include <cmath>
#include <concepts>
template <std::floating_point T>
bool almost_equal(T a, T b) {
return std::abs(a - b) < T(1e-6);
}
template <typename T>
concept Comparable = requires(T value) { almost_equal(value, value); };
static_assert(Comparable<double>);
static_assert(!Comparable<int>);
int main() {
assert(almost_equal(1.0, 1.0));
}
Pro int překladač řekne, že podmínka není splněná, místo stovek řádků chyb uvnitř šablony.
4.2 SFINAE
Substitution Failure Is Not An Error: selže-li dosazení šablonových argumentů v deklaraci, šablona se tiše vyřadí z kandidátů a překlad pokračuje.
Před C++20 se toho využívalo přes std::enable_if:
#include <type_traits>
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
constexpr int kind(T) { return 1; }
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
constexpr int kind(T) { return 2; }
static_assert(kind(1) == 1);
static_assert(kind(1.0) == 2);
Dnes stejnou věc napíšeš přes requires. Starší kód ale SFINAE potkáš, proto je na něj dobré umět odpovědět.
4.3 Vlastní koncept
#include <concepts>
#include <cstddef>
#include <string>
#include <vector>
template <typename T>
concept Sized = requires(const T& value) {
{ value.size() } -> std::convertible_to<std::size_t>;
};
static_assert(Sized<std::string>);
static_assert(Sized<std::vector<int>>);
static_assert(!Sized<int>);
4.4 Statický a dynamický polymorfizmus
| Šablony | Virtuální funkce | |
|---|---|---|
| Rozhodnutí | při překladu | za běhu |
| Režie | žádná, inlining | nepřímé volání |
| Kód | roste s počtem typů | jedna implementace |
| Heterogenní kolekce | ne | ano |
| Chyby | dlouhé hlášky | jasné |
4.5 Type erasure
Skrytí konkrétního typu za jednotné rozhraní, aniž by typ musel dědit od společné třídy. Vnitřně je to šablonový potomek abstraktní třídy.
std::any, std::function a shared_ptr s deleterem to používají. Malá verze:
#include <cassert>
#include <memory>
#include <string>
#include <vector>
class Printable {
struct Base {
virtual ~Base() = default;
virtual std::string text() const = 0;
};
template <typename T>
struct Model : Base {
T value;
explicit Model(T v) : value(std::move(v)) {}
std::string text() const override { return std::to_string(value); }
};
std::unique_ptr<Base> held_;
public:
template <typename T>
explicit Printable(T value) : held_(std::make_unique<Model<T>>(std::move(value))) {}
std::string text() const { return held_->text(); }
};
int main() {
std::vector<Printable> items;
items.emplace_back(1);
items.emplace_back(2.5);
assert(items[0].text() == "1");
assert(items[1].text().starts_with("2.5"));
}
4.6 CRTP
Potomek předá sám sebe jako parametr předka. Předek volá jeho metody bez virtuálních funkcí.
#include <cassert>
template <typename Derived>
struct Countable {
int doubled() const { return 2 * static_cast<const Derived*>(this)->value(); }
};
struct Five : Countable<Five> {
int value() const { return 5; }
};
int main() {
assert(Five{}.doubled() == 10);
}
4.7 Typické chyby
- Chyba uvnitř těla šablony není SFINAE, ale tvrdá chyba. SFINAE se týká jen deklarace.
- Type erasure bez kopírovací podpory:
Printablevýše není kopírovatelný, klonování vyžaduje virtuálníclone. - CRTP přes chybný typ v
static_cast.
4.8 Kvíz
4.9 Příklady na procvičení
Napiš koncept Addable pro typ, který jde sečíst se sebou samým.
Řešení
#include <concepts>
#include <string>
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
};
struct Empty {};
static_assert(Addable<int>);
static_assert(Addable<std::string>);
static_assert(!Addable<Empty>);
Kdy zvolíš std::variant a kdy type erasure?
Řešení
std::variant, když znáš všechny typy předem: bez alokace a bez virtuálních volání. Type erasure, když typy nejsou předem známé a dodá je uživatel.
4.10 Tahák
- Koncepty a
requireslepší neženable_if. - SFINAE: jen deklarace, tělo nechrání.
- Šablona: překlad, rychlé, kód roste.
- Virtuální: běh, jedna implementace, kolekce.
- Type erasure: šablonový konstruktor + abstraktní Base + Model
. - CRTP: statický polymorfizmus přes
Derivedv šabloně.