Skip to content

Instantly share code, notes, and snippets.

@eao197
Last active April 10, 2024 07:55
Show Gist options
  • Save eao197/5e22a5eb37c3750cfac38fd34abc82bc to your computer and use it in GitHub Desktop.
Save eao197/5e22a5eb37c3750cfac38fd34abc82bc to your computer and use it in GitHub Desktop.
Примеры для первой части лекции про шаблоны C++
/***
Реализация шаблона должна быть доступна компилятору.
Поэтому шаблоны размещают в .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