3 Šablony, dedukce a perfect forwarding
- určit, co dedukuje
T,const T&,T&& - použít specializaci a metafunkce
- napsat variadickou šablonu
- vysvětlit
std::forwarda emplace
3.1 Ukázka: proč šablona v hlavičce
Šablona není kód, ale návod. Kód vznikne až instanciací pro konkrétní typ, takže překladač musí vidět definici. Proto šablony patří do hlavičkových souborů.
#include <cassert>
#include <string>
template <typename T>
T twice(const T& value) {
return value + value;
}
int main() {
assert(twice(21) == 42);
assert(twice(std::string("ha")) == "haha");
}
3.2 Dedukce šablonových argumentů
| Parametr | Argument | T |
Typ parametru |
|---|---|---|---|
T |
const int lhodnota |
int |
int |
const T& |
int lhodnota |
int |
const int& |
T& |
const int lhodnota |
const int |
const int& |
T&& |
int lhodnota |
int& |
int& |
T&& |
int rhodnota |
int |
int&& |
Předání hodnotou zahodí const a referenci. Řádky s T&& jsou speciální: šablonový T&& je forwarding reference.
& &, & && a && & kolabují na &. Pouze && && dává &&.
#include <type_traits>
#include <utility>
template <typename T>
constexpr bool deduces_reference(T&&) {
return std::is_lvalue_reference_v<T>;
}
int value = 0;
static_assert(deduces_reference(value));
static_assert(!deduces_reference(std::move(value)));
3.3 Specializace a metafunkce
Šablona, která počítá s typy v době překladu. Výsledek je člen type nebo value.
Implementace remove_reference přes částečnou specializaci, na které pak stojí std::move:
#include <cassert>
#include <type_traits>
template <typename T> struct my_remove_reference { using type = T; };
template <typename T> struct my_remove_reference<T&> { using type = T; };
template <typename T> struct my_remove_reference<T&&> { using type = T; };
template <typename T>
constexpr typename my_remove_reference<T>::type&& my_move(T&& value) noexcept {
return static_cast<typename my_remove_reference<T>::type&&>(value);
}
static_assert(std::is_same_v<my_remove_reference<int&>::type, int>);
int main() {
int x = 1;
int&& r = my_move(x);
r = 2;
assert(x == 2);
}
3.4 Variadické šablony
Parametr typename... Ts přijme libovolný počet typů. Rozbalení se píše .... Od C++17 je nejkratší fold expression.
#include <cassert>
#include <cstddef>
template <typename... Ts>
constexpr auto sum(Ts... values) {
return (values + ...);
}
template <typename... Ts>
constexpr std::size_t count() {
return sizeof...(Ts);
}
static_assert(sum(1, 2, 3) == 6);
static_assert(count<int, char, double>() == 3);
3.5 Perfect forwarding
Obal má předat argument dál bez ztráty kategorie: lhodnotu jako lhodnotu, rhodnotu jako rhodnotu. Bez std::forward by se rhodnota v obalu stala lhodnotou, protože má jméno.
#include <cassert>
#include <string>
#include <utility>
int kind(const std::string&) { return 1; }
int kind(std::string&&) { return 2; }
template <typename T>
int bad(T&& value) { return kind(value); }
template <typename T>
int good(T&& value) { return kind(std::forward<T>(value)); }
int main() {
std::string s = "text";
assert(bad(std::move(s)) == 1); // rhodnota se ztratila
assert(good(std::move(s)) == 2);
assert(good(s) == 1);
}
std::move používej tam, kde víš, že jde o rhodnotu. std::forward jen u forwarding reference.
3.6 Emplace
push_back(x) dostane hotový objekt. emplace_back(args...) předá argumenty rovnou konstruktoru v místě prvku, takže odpadne dočasný objekt.
#include <cassert>
#include <string>
#include <utility>
#include <vector>
struct Named {
std::string name;
int age;
Named(std::string n, int a) : name(std::move(n)), age(a) {}
};
int main() {
std::vector<Named> people;
people.emplace_back("Ada", 36);
assert(people[0].age == 36);
}
3.7 Typické chyby
- Šablona definovaná v
.cpp. T&&u metody třídní šablony není forwarding reference,Tuž je daný.std::forwarddvakrát na stejnou věc.std::moveu forwarding reference: vykradeš lhodnotu volajícího.
3.8 Kvíz
3.9 Příklady na procvičení
Co je T pro template <typename T> void f(T value) při volání s const int& r?
Řešení
int. Předání hodnotou zahodí referenci i const.
Napiš metafunkci my_is_pointer, která pozná ukazatel.
Řešení
template <typename T> struct my_is_pointer { static constexpr bool value = false; };
template <typename T> struct my_is_pointer<T*> { static constexpr bool value = true; };
static_assert(my_is_pointer<int*>::value);
static_assert(!my_is_pointer<int>::value);
Napiš variadickou funkci all_true(args...).
Řešení
template <typename... Ts>
constexpr bool all_true(Ts... values) {
return (values && ...);
}
static_assert(all_true(true, true));
static_assert(!all_true(true, false));
3.10 Tahák
- Šablona se instanciuje, definice patří do hlavičky.
- Hodnotou:
consta reference pryč. T&&s dedukcí = forwarding reference, lhodnota dáT = X&.- Kolaps: cokoli s
&je&. - Metafunkce: specializace,
::type,::value. (args + ...)fold,sizeof...(Ts)počet.std::forward<T>(x)zachová kategorii.emplace_*= konstrukce na místě.