Last active
April 10, 2024 07:55
-
-
Save eao197/5e22a5eb37c3750cfac38fd34abc82bc to your computer and use it in GitHub Desktop.
Примеры для первой части лекции про шаблоны C++
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*** | |
Реализация шаблона должна быть доступна компилятору. | |
Поэтому шаблоны размещают в .hpp-файлах | |
*/ | |
///////////////////////////////////////////////////// | |
/*** | |
Приоритет выбора шаблона. | |
*/ | |
#include <iostream> | |
template<typename T> | |
void DoIt(T, T) { std::cout << "Template DoIt" << std::endl; } | |
void DoIt(int, int) { std::cout << "DoIt for int" << std::endl; } | |
int main() | |
{ | |
int i = 42; | |
char c = 'c'; | |
long l = 100500; | |
DoIt(i, i); | |
DoIt(c, c); | |
DoIt(l, l); | |
} | |
/* | |
И ADL (Argument Dependent Lookup): | |
*/ | |
#include <iostream> | |
template<typename T> | |
void DoIt(T) { std::cout << "Template DoIt" << std::endl; } | |
void DoIt(int) { std::cout << "DoIt for int" << std::endl; } | |
namespace A { | |
struct B {}; | |
void DoIt(B) { std::cout << "DoIt for A::B" << std::endl; } | |
} /* namespace A */ | |
int main() | |
{ | |
int i = 42; | |
char c = 'c'; | |
long l = 100500; | |
DoIt(i); | |
DoIt(c); | |
DoIt(l); | |
A::B b; | |
DoIt(b); | |
} | |
///////////////////////////////////////////////////// | |
/*** | |
Внутри шаблона класса можно не перечислять список его параметров. | |
Тогда как снаружи шаблона класса это делать нужно. | |
*/ | |
#include <iostream> | |
template<typename A, typename B> | |
struct Demo | |
{ | |
Demo & operator=(const Demo &) { return *this; } | |
Demo & operator=(Demo &&); | |
}; | |
template<typename A, typename B> | |
Demo<A, B> & Demo<A, B>::operator=(Demo &&) { return *this; } | |
int main() | |
{ | |
Demo<int, long> a; | |
a = Demo<int, long>{}; | |
} | |
///////////////////////////////////////////////////// | |
/*** | |
Шаблоны классов и виртуальные функции | |
*/ | |
#include <iostream> | |
template<typename T> | |
class Validator | |
{ | |
public: | |
virtual ~Validator() = default; | |
[[nodiscard]] virtual bool | |
check(const T &) = 0; | |
}; | |
template<typename T> | |
class InRangeValidator : public Validator<T> | |
{ | |
T _min; | |
T _max; | |
public: | |
InRangeValidator(T min, T max) : _min{min}, _max{max} {} | |
[[nodiscard]] bool | |
check(const T & v) override | |
{ | |
return v >= _min && v <= _max; | |
} | |
}; | |
int main() | |
{ | |
InRangeValidator<int> v1{0, 50}; | |
std::cout << "int 10: " << v1.check(10) << std::endl; | |
std::cout << "int -1: " << v1.check(-1) << std::endl; | |
InRangeValidator<float> v2{0.0f, 50.0f}; | |
std::cout << "float 10: " << v2.check(10.0f) << std::endl; | |
std::cout << "float -1: " << v2.check(-1.0f) << std::endl; | |
} | |
///////////////////////////////////////////////////// | |
/*** | |
Template variable | |
*/ | |
template<typename T> | |
constexpr T pi{ 3.1415926535897932385 }; // variable template | |
template<typename T> | |
T circular_area(T r) { | |
return pi<T> * r * r; // pi<T> is a variable template instantiation | |
} | |
int main() { | |
circular_area(3.3f); // float | |
circular_area(3.3); // double | |
circular_area(3); // compile error, narrowing conversion with "pi" | |
} | |
///////////////////////////////////////////////////// | |
/*** | |
universal references и perfect forwarding. | |
https://wandbox.org/permlink/gtfN0mD3o8Y4N9X0 | |
*/ | |
#include <iostream> | |
#include <string> | |
using namespace std::string_literals; | |
void f(const std::string & s) { | |
std::cout << "f(const std::string &): " << &s << std::endl; | |
} | |
void f(std::string & s) { | |
std::cout << "f(std::string &): " << &s << std::endl; | |
} | |
void f(std::string && s) { | |
std::cout << "f(std::string &&): " << &s << std::endl; | |
} | |
template<typename T> | |
void g(T obj) { | |
std::cout << "g: " << &obj << " -> " << std::flush; | |
f(obj); | |
} | |
template<typename T> | |
void u(T && obj) { | |
std::cout << "u: " << &obj << " -> " << std::flush; | |
f(obj); | |
} | |
template<typename T> | |
void u2(T && obj) { | |
std::cout << "u2: " << &obj << " -> " << std::flush; | |
f(std::forward<T>(obj)); | |
} | |
int main() | |
{ | |
std::string s1; | |
const std::string s2; | |
std::cout << "s1: " << &s1 << std::endl; | |
std::cout << "s2: " << &s2 << std::endl; | |
f(s1); | |
f(s2); | |
std::cout << "---" << std::endl; | |
g(s1); | |
g(s2); | |
g("Hello"s); | |
std::cout << "---" << std::endl; | |
u(s1); | |
u(s2); | |
u("Hello"s); | |
std::cout << "---" << std::endl; | |
u2(s1); | |
u2(s2); | |
u2("Hello"s); | |
} | |
/* | |
Вот здесь: | |
template<typename T> void u(T && obj); | |
`T&&` обозначает forwarding reference. | |
А вот здесь уже нет: | |
template<typename T> | |
struct Demo { | |
Demo(T && obj); // Это уже rvalue reference. | |
}; | |
Поэтому для того, чтобы класс Demo в своем конструкторе мог обрабатывать | |
разные типы ссылок на T приходится делать шаблонный конструктор у Demo: | |
template<typename T> | |
struct Demo { | |
template<typename TT> | |
Demo(TT && obj); // А вот это уже forwarding reference. | |
}; | |
Например: | |
*/ | |
#include <iostream> | |
#include <string> | |
using namespace std::string_literals; | |
template<typename T> | |
struct Demo { | |
T _value; | |
Demo(T && value) : _value{value} {} | |
}; | |
template<typename T> | |
struct Demo2 { | |
T _value; | |
template<typename U> | |
Demo2(U && value) : _value{std::forward<U>(value)} {} | |
}; | |
int main() | |
{ | |
const std::string s2; | |
Demo2<std::string> demo2{s2}; | |
} | |
///////////////////////////////////////////////////// | |
/*** | |
Использование using с шаблонами. | |
*/ | |
#include <iostream> | |
#include <vector> | |
#include <list> | |
template<typename T, typename Container> | |
class Stack { | |
Container _values; | |
public: | |
void push_back(T o) { _values.push_back(std::move(o)); } | |
}; | |
template<typename T> | |
using VectorBasedStack = Stack<T, std::vector<T>>; | |
template<typename T> | |
using ListBasedStack = Stack<T, std::list<T>>; | |
int main() | |
{ | |
VectorBasedStack<int> int_stack; | |
int_stack.push_back(0); | |
ListBasedStack<long> long_stack; | |
long_stack.push_back(0l); | |
} | |
///////////////////////////////////////////////////// | |
/*** | |
При вызове шаблонов функций можно указывать некоторые параметры шаблона явно. Например, вот в таких случаях: | |
*/ | |
template<typename R, typename T> | |
R add(T a, T b) { | |
return a + b; | |
} | |
add<long>(1, 2); | |
// Здесь R -- это long, а T -- это int. | |
///////////////////////////////////////////////////// | |
/*** | |
Ключевое слово auto в списке параметров шаблона. | |
Начиная с C++17. | |
*/ | |
#include <iostream> | |
template<auto V> | |
struct ConstValue { | |
static constexpr auto v = V; | |
}; | |
int main() | |
{ | |
std::cout << ConstValue<42>::v << std::endl; | |
std::cout << ConstValue<100500ll>::v << std::endl; | |
} | |
// До C++17 нужно было делать как-то так: | |
#include <iostream> | |
template<typename T, T V> | |
struct ConstValue { | |
static constexpr auto v = V; | |
}; | |
int main() | |
{ | |
std::cout << ConstValue<int, 42>::v << std::endl; | |
std::cout << ConstValue<long long, 100500ll>::v << std::endl; | |
} | |
///////////////////////////////////////////////////// | |
/*** | |
Параметры шаблона могут зависеть друг от друга. | |
*/ | |
#include <iostream> | |
#include <vector> | |
#include <utility> | |
template<typename K, typename V, typename P = std::pair<K,V>> | |
struct PairsVector { | |
std::vector<P> _data; | |
}; | |
int main() | |
{ | |
PairsVector<char, int> v; | |
v._data.emplace_back('c', 11); | |
v._data.emplace_back('z', 44); | |
} | |
// Можно подсунуть свой тип Pair: | |
#include <iostream> | |
#include <vector> | |
#include <utility> | |
template<typename K, typename V, typename P = std::pair<K,V>> | |
struct PairsVector { | |
std::vector<P> _data; | |
}; | |
template<typename K, typename V> | |
struct KeyValue { | |
K _key{}; | |
V _value{}; | |
KeyValue() = default; | |
KeyValue(K key, V value) : _key{key}, _value{value} {} | |
}; | |
int main() | |
{ | |
PairsVector<char, int, KeyValue<char, int>> v; | |
v._data.emplace_back('c', 11); | |
v._data.emplace_back('z', 44); | |
} | |
///////////////////////////////////////////////////// | |
/*** | |
Специализации шаблонов | |
Пример специализации для функции: | |
*/ | |
#include <iostream> | |
#include <string> | |
#include <cstring> | |
template<typename T> | |
bool is_equal(T a, T b) { | |
std::cout << "*default* "; | |
return a == b; | |
} | |
template<> | |
bool is_equal<const char *>(const char * a, const char * b) { | |
std::cout << "*const char ptr* "; | |
return 0 == std::strcmp(a, b); | |
} | |
int main() | |
{ | |
std::cout << "int: " << is_equal(0, 0) << std::endl; | |
std::cout << "char *: " << is_equal("0", "1") << std::endl; | |
} | |
// Пример специализации для класса: | |
#include <iostream> | |
#include <string> | |
#include <cstring> | |
template<typename T> | |
struct ValueHolder | |
{ | |
T _value; | |
ValueHolder(T value) : _value{std::move(value)} {} | |
}; | |
template<> | |
struct ValueHolder<const char *> | |
{ | |
std::string _value; | |
ValueHolder(const char * value) : _value{value} {} | |
}; | |
ValueHolder<int> MakeHolder1(int v) { | |
return {v}; | |
} | |
ValueHolder<const char*> MakeHolder2(int v) { | |
return {std::to_string(v).c_str()}; | |
} | |
int main() | |
{ | |
auto v1 = MakeHolder1(0); | |
std::cout << v1._value << std::endl; | |
auto v2 = MakeHolder2(0); | |
std::cout << v2._value << std::endl; | |
} | |
// Для функций доступна только полная специализация. | |
// Для классов возможна и частичная. | |
template<typename T, bool CompactType> | |
class ValueHolder { ... }; | |
template<typename T> | |
class ValueHolder<T, true> { ... }; | |
// Иногда хочется сделать частичную специализацию функции, но это невозможно. | |
// Тогда можно прибегнуть к вспомогательному классу: | |
namespace f_impl | |
{ | |
template<typename T, typename U> | |
struct helper { | |
static void f(T a, U b) {...} | |
}; | |
template<typename T> | |
struct helper<T, int> { | |
static void f(T a, int b) {...} | |
}; | |
} /* namespace f_impl */ | |
template<typename T, typename U> | |
void f(T a, U b) { | |
f_impl::helper<T, U>::f(a, b); | |
} | |
///////////////////////////////////////////////////// | |
/*** | |
Простейший type-traits своими руками на основе специализации шаблона класса. | |
*/ | |
#include <iostream> | |
#include <cmath> | |
template<typename T> | |
struct HasPreciseEquality | |
{ | |
static constexpr bool value = true; | |
}; | |
template<> | |
struct HasPreciseEquality<float> | |
{ | |
static constexpr bool value = false; | |
}; | |
template<> | |
struct HasPreciseEquality<double> | |
{ | |
static constexpr bool value = false; | |
}; | |
template<typename T> | |
bool is_equal(T a, T b) | |
{ | |
if constexpr(HasPreciseEquality<T>::value) { | |
return a == b; | |
} | |
else { | |
return std::abs(std::abs(a) - std::abs(b)) < 0.0001; | |
} | |
} | |
int main() | |
{ | |
std::cout << " int: " << is_equal(1, 1) << std::endl; | |
std::cout << "double: " << is_equal(0.0, 0.0000002) << std::endl; | |
} | |
// Чтобы не выписывать HasPreciseEquality<T>::value постоянно, | |
// можно использовать template variable: | |
template<typename T> | |
constexpr bool HasPreciseEquality_v = HasPreciseEquality<T>::value; | |
template<typename T> | |
bool is_equal(T a, T b) | |
{ | |
if constexpr(HasPreciseEquality_v<T>) { | |
return a == b; | |
} | |
else { | |
return std::abs(std::abs(a) - std::abs(b)) < 0.0001; | |
} | |
} | |
// Можно посредством специализации выводить типы. | |
// Например, если нам нужно уметь делать unsigned вариант типа: | |
template<typename T> | |
struct MakeUnsigned; | |
template<> | |
struct MakeUnsigned<char> { | |
using type = unsigned char; | |
}; | |
template<> | |
struct MakeUnsigned<short> { | |
using type = unsigned short; | |
}; | |
template<> | |
struct MakeUnsigned<int> { | |
using type = unsigned int; | |
}; | |
int main() | |
{ | |
std::cout << typeid(MakeUnsigned<char>::type).name() << std::endl; | |
std::cout << typeid(MakeUnsigned<int>::type).name() << std::endl; | |
} | |
// Чтобы не приходилось постоянно выписывать MakeUnsigned<T>::type | |
// можно сделать удобный using: | |
template<typename T> | |
using MakeUnsigned_t = typename MakeUnsigned<T>::type; | |
int main() | |
{ | |
std::cout << typeid(MakeUnsigned_t<char>).name() << std::endl; | |
std::cout << typeid(MakeUnsigned_t<int>).name() << std::endl; | |
} | |
// Ключевой момент здесь -- это использование `typename` при определении using-а. | |
// Есть стандартный заголовок type_traits, в котором уже куча всего реализована | |
// В частности, есть готовый std::make_unsigned<T> и его сокращенная | |
// форма std::make_unsigned_t<T>. | |
// static_assert для проверки наших предположений прямо в коде: | |
#include <type_traits> | |
... | |
int main() | |
{ | |
static_assert(std::is_same_v<MakeUnsigned_t<char>, unsigned char>); | |
static_assert(std::is_same_v<MakeUnsigned_t<int>, unsigned int>); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment