Last active
April 10, 2024 10:03
-
-
Save eao197/246470e171756322c4c612092f51e863 to your computer and use it in GitHub Desktop.
Примеры для второй части лекции про шаблоны C++
This file contains hidden or 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
/*** | |
SFINAE: Substitution failure is not an error | |
*/ | |
// В основном базируется на std::enable_if | |
// https://en.cppreference.com/w/cpp/types/enable_if | |
#include <type_traits> | |
int main() { | |
using T1 = std::enable_if<8 == sizeof(void*), long>::type; | |
using T2 = std::enable_if<4 == sizeof(int)>::type; | |
static_assert(std::is_same_v<long, T1>); | |
static_assert(std::is_same_v<void, T2>); | |
} | |
// Обычно вместо std::enable_if<C, T>::type пишут std::enable_if_t<C, T>. | |
#include <iostream> | |
#include <cmath> | |
template<typename T> | |
std::enable_if_t<std::is_floating_point_v<T>, bool> | |
is_equal(T a, T b) | |
{ | |
return std::abs(std::abs(a) - std::abs(b)) < 0.0001; | |
} | |
template<typename T> | |
std::enable_if_t<std::is_integral_v<T>, bool> | |
is_equal(T a, T b) | |
{ | |
return a == b; | |
} | |
int main() | |
{ | |
std::cout << " int: " << is_equal(1, 1) << std::endl; | |
std::cout << "double: " << is_equal(0.0, 0.0000002) << std::endl; | |
} | |
/*** | |
Может иметь несколько форм: | |
enable_if для возвращаемого значения; | |
enable_if для параметра шаблона по умолчанию; | |
enable_if для аргумента функции/метода по умолчанию. | |
*/ | |
// Пример использования enable_if в параметре шаблона. | |
// Чтобы можно было написать auto в качестве типа возвращаемого значения. | |
#include <iostream> | |
#include <type_traits> | |
template<typename A, typename B, | |
typename D = std::enable_if_t<std::is_integral_v<A> && std::is_integral_v<B>> > | |
auto | |
add(A a, B b) { return a+b; } | |
template<typename A, typename B, | |
typename D = std::enable_if_t<std::is_floating_point_v<A> && std::is_floating_point_v<B>> > | |
float | |
add(A a, B b) { return static_cast<float>(a +b); } | |
int main() { | |
auto r1 = add(1, 2l); | |
static_assert(std::is_same_v<long, decltype(r1)>); | |
auto r2 = add(2.0, 3.0f); | |
static_assert(std::is_same_v<float, decltype(r2)>); | |
} | |
// При определении шаблонного конструктора. | |
// Пример простого шаблонного конструктора. | |
#include <iostream> | |
#include <type_traits> | |
#include <string> | |
template<typename T> | |
class ValueHolder | |
{ | |
T _value; | |
public: | |
template<typename U> | |
ValueHolder(U && v) : _value{std::forward<U>(v)} | |
{ | |
std::cout << "template constructor" << std::endl; | |
} | |
[[nodiscard]] const T & | |
value() const { return _value; } | |
}; | |
int main() { | |
ValueHolder<std::string> v1{"Hello"}; | |
std::cout << "v1: " << v1.value() << std::endl; | |
ValueHolder<std::string> v2{std::string{"World"}}; | |
std::cout << "v2: " << v2.value() << std::endl; | |
} | |
// Ведет к ошибке компиляции вот в таком случае: | |
// | |
// ValueHolder<std::string> v3{v2}; | |
// | |
// Версия с SFINAE | |
#include <iostream> | |
#include <type_traits> | |
#include <string> | |
template<typename T> | |
class ValueHolder | |
{ | |
T _value; | |
public: | |
template<typename U, | |
typename Guard = std::enable_if_t< | |
!std::is_same_v<std::decay_t<U>, ValueHolder> | |
> | |
> | |
ValueHolder(U && v) : _value{std::forward<U>(v)} | |
{ | |
std::cout << "template constructor" << std::endl; | |
} | |
[[nodiscard]] const T & | |
value() const { return _value; } | |
}; | |
int main() { | |
ValueHolder<std::string> v1{"Hello"}; | |
std::cout << "v1: " << v1.value() << std::endl; | |
ValueHolder<std::string> v2{std::string{"World"}}; | |
std::cout << "v2: " << v2.value() << std::endl; | |
ValueHolder<std::string> v3{v2}; | |
std::cout << "v3: " << v3.value() << std::endl; | |
} | |
// Используется std::decay: https://en.cppreference.com/w/cpp/types/decay | |
// | |
// Performs the type conversions equivalent to the ones performed when passing | |
// function arguments by value. | |
// | |
// В частности, для нашего случая: Otherwise, the member typedef type is | |
// std::remove_cv<std::remove_reference<T>::type>::type. | |
// | |
// SFINAE посредством параметра по умолчанию | |
// | |
#include <iostream> | |
#include <type_traits> | |
template<typename A, typename B> | |
auto | |
add(A a, B b, | |
std::enable_if_t<std::is_integral_v<A> && std::is_integral_v<B>> * = nullptr) | |
{ return a+b; } | |
template<typename A, typename B> | |
float | |
add(A a, B b, | |
std::enable_if_t<std::is_floating_point_v<A> && std::is_floating_point_v<B>> * = nullptr ) | |
{ return static_cast<float>(a+b); } | |
int main() { | |
auto r1 = add(1, 2l); | |
static_assert(std::is_same_v<long, decltype(r1)>); | |
auto r2 = add(2.0, 3.0f); | |
static_assert(std::is_same_v<float, decltype(r2)>); | |
} | |
///////////////////////////////////////////////////// | |
/*** | |
Случай, когда приходится использовать нотацию this->member. | |
*/ | |
#include <iostream> | |
template<typename T> | |
struct Base { | |
T _x; | |
}; | |
template<typename T> | |
struct Derived : public Base<T> { | |
void print() const { | |
std::cout << _x << std::endl; | |
} | |
}; | |
int main() { | |
Derived<int> v{10}; | |
v.print(); | |
} | |
// Ошибка компиляции, т.к. в Derived<T>::print имя _x неизвестно. | |
// Возможные исправления: | |
// Вариант №1 | |
void print() const { | |
std::cout << this->_x << std::endl; | |
} | |
// Вариант №2 | |
template<typename T> | |
struct Derived : public Base<T> { | |
using Base<T>::_x; | |
void print() const { | |
std::cout << _x << std::endl; | |
} | |
}; | |
///////////////////////////////////////////////////// | |
/*** | |
Лямбда-функции и шаблоны | |
Если нам нужно куда-то передать лямбду в качестве параметра, то либо std::function, | |
либо шаблон. Но std::function -- это аналог std::shared_ptr, за std::function может | |
скрываться невидимая пользователю аллокация памяти. | |
*/ | |
#include <iostream> | |
template<typename L> | |
auto CallLambda(L && lambda) { return lambda(); } | |
struct Demo { | |
Demo() = default; | |
Demo(const Demo &) { std::cout << "Demo copy ctor" << std::endl; } | |
Demo & operator=(const Demo &) { | |
std::cout << "Demo copy operator" << std::endl; | |
return *this; | |
} | |
}; | |
template<typename L> | |
decltype(auto) CallLambda2(L && lambda) { return lambda(); } | |
int main() | |
{ | |
Demo a; | |
const auto & b1 = CallLambda([&a]() -> Demo & { return a;}); | |
(void) b1; | |
const auto & b2 = CallLambda2([&a]() -> Demo & { return a;}); | |
if(&a == &b2) std::cout << "same object" << std::endl; | |
} | |
// Отдельная история происходит когда нам нужно сохранить лямбду как поле | |
// какого-то класса. Простой вариант -- std::function, но если не подходит, то... | |
// Проблемный вариант: | |
#include <iostream> | |
#include <type_traits> | |
template<typename Lambda> | |
class LambdaHolder | |
{ | |
Lambda _lambda; | |
public: | |
LambdaHolder(Lambda && lambda) : _lambda{std::move(lambda)} {} | |
decltype(auto) Call(int v) { return _lambda(v); } | |
}; | |
template<typename Lambda> | |
[[nodiscard]] auto | |
MakeHolder(Lambda && lambda) | |
{ | |
using LambdaType = std::decay_t<Lambda>; | |
return LambdaHolder<LambdaType>{std::move(lambda)}; | |
} | |
void Func(int v) { std::cout << "func: " << v << std::endl; } | |
int main() { | |
const auto first = [](int v) { std::cout << "first: " << v << std::endl; }; | |
auto second = [](int v) { | |
std::cout << "second: " << v << std::endl; | |
return v+1; | |
}; | |
MakeHolder(Func).Call(0); | |
// MakeHolder(first).Call(1); | |
MakeHolder(second).Call(2); | |
MakeHolder([&first](int v) { first(v+10); }).Call(3); | |
} | |
// Проблема в том, что first -- это const. | |
// Решение: | |
template<typename Lambda> | |
class LambdaHolder | |
{ | |
Lambda _lambda; | |
public: | |
template<typename ActualLambda> | |
LambdaHolder(ActualLambda && lambda) : _lambda{std::forward<ActualLambda>(lambda)} {} | |
decltype(auto) Call(int v) { return _lambda(v); } | |
}; | |
///////////////////////////////////////////////////// | |
/*** | |
Variadic templates | |
*/ | |
template<typename... TArgs> | |
void f(TArgs ...args) {} | |
// Разница между TArgs... args и TArgs && ...args: | |
#include <iostream> | |
template<typename... Args> | |
void Handle(Args... /*args*/) {} | |
struct Demo { | |
Demo() = default; | |
Demo(const Demo &) { std::cout << "Demo copy ctor" << std::endl; } | |
Demo & operator=(const Demo &) { | |
std::cout << "Demo copy operator" << std::endl; | |
return *this; | |
} | |
}; | |
int main() { | |
Demo d1, d2, d3; | |
Handle(d1, d2, d3); | |
} | |
// Второй вариант: | |
#include <iostream> | |
template<typename... Args> | |
void Handle(Args && .../*args*/) {} | |
struct Demo { | |
Demo() = default; | |
Demo(const Demo &) { std::cout << "Demo copy ctor" << std::endl; } | |
Demo & operator=(const Demo &) { | |
std::cout << "Demo copy operator" << std::endl; | |
return *this; | |
} | |
}; | |
int main() { | |
Demo d1, d2, d3; | |
Handle(d1, d2, d3); | |
} | |
// Для того, чтобы развернуть parameter pack нужно записать pack... | |
#include <iostream> | |
template<typename... Args> | |
void Handle(Args && ...args) { | |
int values[]{args...}; | |
for(int v : values) std::cout << v << std::endl; | |
} | |
int main() { | |
Handle(5, 6, 7, 8); | |
} | |
// Узнать размер pack-а можно посредством sizeof...(pack): | |
#include <iostream> | |
#include <array> | |
template<typename... Args> | |
void Handle(Args && ...args) { | |
std::array<int, sizeof...(args)> values{args...}; | |
for(int v : values) std::cout << v << std::endl; | |
} | |
int main() { | |
Handle(5, 6, 7, 8); | |
} | |
// При распаковке pack-а можно делать и вызов функции: | |
#include <iostream> | |
#include <array> | |
int square(int v) { return v * v; } | |
template<typename... Args> | |
void Handle(Args && ...args) { | |
std::array<int, sizeof...(args)> values{square(args)...}; | |
for(int v : values) std::cout << v << std::endl; | |
} | |
int main() { | |
Handle(5, 6, 7, 8); | |
} | |
// Можно завершать рекурсию посредством терминальных функций и | |
// посредством if constexpr. | |
// Терминальная функция: | |
#include <iostream> | |
#include <array> | |
namespace min_impl { | |
template<typename T> | |
T GetMinElement(T currentMin) { | |
return currentMin; | |
} | |
template<typename T, typename... Tail> | |
T GetMinElement(T currentMin, T toCompare, Tail ...tail) { | |
return GetMinElement(currentMin < toCompare ? currentMin : toCompare, tail...); | |
} | |
} /* namespace min_impl */ | |
template<typename T, typename... Tail> | |
T MinElement(T head, Tail ...tail) { | |
return min_impl::GetMinElement(head, tail...); | |
} | |
int main() { | |
std::cout << MinElement(10) << std::endl; | |
std::cout << MinElement(10, 0) << std::endl; | |
std::cout << MinElement(-1, 0) << std::endl; | |
std::cout << MinElement(-1, 0, -2) << std::endl; | |
std::cout << MinElement(10, 20, 30, 5, 0, -1, 100, 50) << std::endl; | |
std::cout << MinElement(10, 20, 30, 40, 50) << std::endl; | |
std::cout << MinElement(50, 40, 30, 20, 10) << std::endl; | |
} | |
// Использование if constexpr: | |
#include <iostream> | |
#include <array> | |
namespace min_impl { | |
template<typename T, typename... Tail> | |
T GetMinElement(T currentMin, T toCompare, Tail ...tail) { | |
if constexpr(0 == sizeof...(tail)) | |
return toCompare < currentMin ? toCompare : currentMin; | |
else | |
return GetMinElement(GetMinElement(currentMin, toCompare), tail...); | |
} | |
} /* namespace min_impl */ | |
template<typename T, typename... Tail> | |
T MinElement(T head, Tail ...tail) { | |
if constexpr(0 == sizeof...(tail)) | |
return head; | |
else | |
return min_impl::GetMinElement(head, tail...); | |
} | |
int main() { | |
std::cout << MinElement(10) << std::endl; | |
std::cout << MinElement(10, 0) << std::endl; | |
std::cout << MinElement(-1, 0) << std::endl; | |
std::cout << MinElement(-1, 0, -2) << std::endl; | |
std::cout << MinElement(10, 20, 30, 5, 0, -1, 100, 50) << std::endl; | |
std::cout << MinElement(10, 20, 30, 40, 50) << std::endl; | |
std::cout << MinElement(50, 40, 30, 20, 10) << std::endl; | |
} | |
// Использование variadic templates для perfect forwarding. | |
#include <iostream> | |
#include <memory> | |
#include <string> | |
template<typename T, typename... Args> | |
std::unique_ptr<T> MyMakeUnique(Args && ...args) { | |
return std::unique_ptr<T>{ new T(std::forward<Args>(args)...) }; | |
} | |
int main() { | |
auto s1 = MyMakeUnique<std::string>(std::size_t{20}, 'A'); | |
std::cout << *s1 << std::endl; | |
auto s2 = MyMakeUnique<std::string>(*s1 + "-tail"); | |
std::cout << *s2 << std::endl; | |
} | |
// Можно обратить внимание на использование круглых скобок при вызове | |
// конструктора T внутри MyMakeUnique. | |
///////////////////////////////////////////////////// | |
/*** | |
Fold expression -- https://en.cppreference.com/w/cpp/language/fold | |
( pack op ... ) (1) -> Unary right fold. | |
( ... op pack ) (2) -> Unary left fold | |
( pack op ... op init ) (3) -> Binary right fold | |
( init op ... op pack ) (4) -> Binary left fold | |
Раскрывается в: | |
1) Unary right fold (E op ...) -> (E1 op (... op (EN-1 op EN))) | |
2) Unary left fold (... op E) -> (((E1 op E2) op ...) op EN) | |
3) Binary right fold (E op ... op I) -> (E1 op (... op (EN−1 op (EN op I)))) | |
4) Binary left fold (I op ... op E) -> ((((I op E1) op E2) op ...) op EN) | |
*/ | |
#include <iostream> | |
template<typename... Args> | |
auto sum(Args ...args) { | |
return (... + args); | |
} | |
template<typename T> | |
T square(T v) { return v * v; } | |
template<typename... Args> | |
auto sum_squares(Args ...args) { | |
return (... + square(args)); | |
} | |
int main() { | |
std::cout << sum(1, 2, 3, 4.3) << std::endl; | |
std::cout << sum_squares(1, 2, 3, 4.3) << std::endl; | |
} | |
// Нужно учитывать приоритеты операций если в fold expression есть начальное значение: | |
template<typename... Args> | |
int sum(Args&&... args) | |
{ | |
// return (args + ... + 1 * 2); // Error: operator with precedence below cast | |
return (args + ... + (1 * 2)); // OK | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment