From 1ab37dc09e83e6264830f661a8f6f828e3c27f06 Mon Sep 17 00:00:00 2001 From: Ivan Kostin Date: Wed, 20 Aug 2025 00:41:35 +0300 Subject: [PATCH 01/12] grammar 1 --- src/09_allocations_optimizations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/09_allocations_optimizations.md b/src/09_allocations_optimizations.md index 1517d76..d1781e4 100644 --- a/src/09_allocations_optimizations.md +++ b/src/09_allocations_optimizations.md @@ -6,13 +6,13 @@ — Я хочу. И вставлю в отдельный файл. ## Аллокация памяти на Linux. -На Linux страницы памяти выделяются через [`mmap`](http://man7.org/linux/man-pages/man2/mmap.2.html), а освобождают через `munmap`. Можно заметить интересный эффект: если мы просто делаем выделение памяти кучу раз, это будет относительно быстро, а когда мы начнём к выделенной памяти обращаться, ты мы зависнем надолго. Почему так? Точнее на вопрос «почему» ответ простой — потому что **ОС даёт нам память только тогда, когда мы её используем**, а вот «зачем» — интересный вопрос. Дело в том, что обычно у нас нет свободной оперативной памяти, она вся либо отдана другим программам, либо используется как дисковый кэш. Поэтому когда кто-то просит память, вам придётся сбрасывать дисковый кэш, либо выгружать кого-то в swap-файлы. А если вдруг ваша программа будет использовать выделенную память как-то потом (или не будет вообще), то давать ей память сразу невыгодно, поэтому ОС даёт её при обращении и маленькими кусочками (ведь используете вы маленькими кусочками, а `mmap` выделяет сразу кучу памяти). Кстати, **в `mmap` есть специальный флаг (`MAP_POPULATE`), который заставляет выделять память сразу**. +На Linux страницы памяти выделяются через [`mmap`](http://man7.org/linux/man-pages/man2/mmap.2.html), а освобождают через `munmap`. Можно заметить интересный эффект: если мы просто делаем выделение памяти кучу раз, это будет относительно быстро, а когда мы начнём к выделенной памяти обращаться, то мы зависнем надолго. Почему так? Точнее на вопрос «почему» ответ простой — потому что **ОС даёт нам память только тогда, когда мы её используем**, а вот «зачем» — интересный вопрос. Дело в том, что обычно у нас нет свободной оперативной памяти, она вся либо отдана другим программам, либо используется как дисковый кэш. Поэтому когда кто-то просит память, вам придётся сбрасывать дисковый кэш, либо выгружать кого-то в swap-файлы. А если вдруг ваша программа будет использовать выделенную память как-то потом (или не будет вообще), то давать ей память сразу невыгодно, поэтому ОС даёт её при обращении и маленькими кусочками (ведь используете вы маленькими кусочками, а `mmap` выделяет сразу кучу памяти). Кстати, **в `mmap` есть специальный флаг (`MAP_POPULATE`), который заставляет выделять память сразу**. При этом, если сделать так, то работать это будет быстрее, чем если выделить память обычным образом, а потом к ней пообращаться, потому что с флагом не будет происходить множество переходов между userspace'ос и ОС при каждом обращении. Хорошо, а знаете ли вы, что **в `mmap` больше всего времени занимает обнуление выделенной страницы**. Зачем? А вдруг там кто-то криптографию оставил. Кто-то, кого вы только что убили, например. Но вообще под это тоже есть специальный флажок `MAP_UNINITIALIZED` (но только для анонимных страниц), который был создан специально для миниатюрных устройств. **Работает `MAP_UNINITIALIZED`, правда, только в том случае, если ОС была собрана специальным образом**. Если, кстати, делать `mmap` без флага `MAP_POPULATE`, то ещё больше времени, чем на зануление, будет тратиться на передачу обращения между ОС и userspace'ом. Как подобное ленивое выделение работает с точки зрения страничной адресации? Вот так: при запросе выделения памяти через `mmap` ОС не сразу обращается к процессору, а помечает у себя страницы как "*заказанные*". Затем, когда происходит обращение к памяти, получаем ошибку *page fault*, ОС проверяет, если страница выделена, что она мапит её в физическую память, иначе это ошибка. -Как этим можно пользоваться? Типовое использование — это не выделение одной страницы, а сразу порции памяти, которая разбивается на мелкие куски и выдается `malloc`'ом. Когда они выдаются в программе, они мапаются в физическую память. Ещё мапить сразу в физическую память не очень полезно, так как память, не принадлежащая программой, используется ОС, например, как дисковый кэш. +Как этим можно пользоваться? Типовое использование — это выделение не одной страницы, а сразу порции памяти, которая разбивается на мелкие куски и выдается `malloc`'ом. Когда они выдаются в программе, они мапаются в физическую память. Ещё мапить сразу в физическую память не очень полезно, так как память, не принадлежащая программе, используется ОС, например, как дисковый кэш. Зачем нам это знать? Это полезно, если мы что-то бенчмаркаем и выделяем большой массив, первый прогон какого-нибудь алгоритма может быть дольше остальных из-за того, что он сначала не помаплен в память. From 61b1d3357525923a7e4c301f8c06a9730fcf5813 Mon Sep 17 00:00:00 2001 From: Ivan Kostin Date: Wed, 20 Aug 2025 01:18:36 +0300 Subject: [PATCH 02/12] grammar 2 --- src/10_libraries.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/10_libraries.md b/src/10_libraries.md index e4c9775..4945f37 100644 --- a/src/10_libraries.md +++ b/src/10_libraries.md @@ -24,6 +24,7 @@ int main(){ } ``` ```c++ +// five #include int sum(int a, int b); From dad108d67d132e35d16177464a96a83300c5842e Mon Sep 17 00:00:00 2001 From: Ivan Kostin Date: Wed, 20 Aug 2025 22:40:41 +0300 Subject: [PATCH 03/12] grammar 3 --- src/14_templates.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/14_templates.md b/src/14_templates.md index 59527e8..ea5ca7f 100644 --- a/src/14_templates.md +++ b/src/14_templates.md @@ -165,7 +165,7 @@ void baz(int*) {} ```c++ int main() { - foo(nullptr); + baz(nullptr); } ``` Давайте подумаем, работает ли это, если мы включим `ENABLE_TEMPLATE`. А вот не работает, потому что непонятно, чему равно `T`. А вот с перегрузкой всё работает (выбирается перегрузка). Почему это так работает, хочется спросить? @@ -177,9 +177,9 @@ int main() { Подробнее про *Template argument deduction* на [cppreference](https://en.cppreference.com/w/cpp/language/template_argument_deduction). -Поэтому когда мы подставляем `nullptr`, то из него нельзя понять, на какой тип он указывает, поэтому *deduction* провалится, и мы получаем ошибку. Если же есть `void foo(int*)`, то выбирается он, как единственный подходящий. +Поэтому когда мы подставляем `nullptr`, то из него нельзя понять, на какой тип он указывает, поэтому *deduction* провалится, и мы получаем ошибку. Если же есть `void baz(int*)`, то выбирается он, как единственный подходящий. -Кстати, можно немного изменить работу с перегрузками. Можно вызывать функции не как `foo(...)`, а как `foo<>(...)`. В таком случае вы явно отбросите всё, что не является шаблоном, а значит выбирать будете только из специализаций. +Кстати, можно немного изменить работу с перегрузками. Можно вызывать функции не как `baz(...)`, а как `baz<>(...)`. В таком случае вы явно отбросите всё, что не является шаблоном, а значит выбирать будете только из специализаций. ## Non-type template parameter. @@ -439,7 +439,7 @@ int main(){ return 0; } ``` -Без *main.cpp* компилируется, так как у `a` не вызывался деструктор, поэтому он не инстанцировался. С *main.cpp* компилятор генерирует деструктор, который вызывает деструкторы всех членов класса, а там `unigue_ptr`, у которого при компиляции будет инстанцироваться деструктор. В `unique_ptr` есть специальная проверка, что если удаляется incomplete type (а у нас `object` именно таковой), то это ошибка.\ +Без *main.cpp* компилируется, так как у `a` не вызывался деструктор, поэтому он не инстанцировался. С *main.cpp* компилятор генерирует деструктор, который вызывает деструкторы всех членов класса, а там `unique_ptr`, у которого при компиляции будет инстанцироваться деструктор. В `unique_ptr` есть специальная проверка, что если удаляется incomplete type (а у нас `object` именно таковой), то это ошибка.\ Как решить проблему? Сделать объявление деструктора в *mytype.h*, а определить его там, где `object` — complete тип (то есть в *mytype.cpp*). Ещё пример: @@ -470,7 +470,7 @@ struct derived : base { В предыдущем примере тот же самый эффект: так как `derived` шаблонный, то он не инстанцируется сразу, но когда мы инстанцируем `derived`, то он создаётся как incomplete (complete он станет после подстановки базовых классов), происходит подстановка base и получаем ошибку. -В конексте обсуждённого выше может быть интересно прочитать про идиому [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). +В контексте обсуждённого выше может быть интересно прочитать про идиому [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). ### Явное инстанцирование шаблонов. From 77a892d3d8246d4bf383e7bfcc82f64b3704c2a3 Mon Sep 17 00:00:00 2001 From: Ivan Kostin Date: Thu, 21 Aug 2025 13:00:11 +0300 Subject: [PATCH 04/12] example upd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В контексте примера было не понятно, что такое ADL, дополнил пример --- src/16_namespaces_using_adl.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/16_namespaces_using_adl.md b/src/16_namespaces_using_adl.md index df7ceb8..4acc83a 100644 --- a/src/16_namespaces_using_adl.md +++ b/src/16_namespaces_using_adl.md @@ -296,6 +296,11 @@ int main() { Немного best practices о том, как надо делать `swap`: ```c++ +namespace my_lib { + struct big_integer {}; + void swap(big_integer&, big_integer&) {// ... //} +} + template void foo(T a, T b) { // ... @@ -303,6 +308,14 @@ void foo(T a, T b) { swap(a, b); // ... } + +int main() { + my_lib::big_integer a1, b1; + foo(a1, b1); // выбирается my_lib::swap + + int a2, b2; + foo(a2, b2); // выбирается std::swap +} ``` Теперь у нас получается шаблонный `std::swap` и, возможно, есть не-шаблонный ADL. - Если есть ADL, выбирается он, потому что из шаблонного и не-шаблонного выбирается второй. From a180d5b47e6e0f955f5456cb04c1a492ad6b83ec Mon Sep 17 00:00:00 2001 From: Ivan Kostin Date: Thu, 21 Aug 2025 23:00:18 +0300 Subject: [PATCH 05/12] grammar 4 --- src/17_move_rvalue.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/17_move_rvalue.md b/src/17_move_rvalue.md index 045fadf..08d4969 100644 --- a/src/17_move_rvalue.md +++ b/src/17_move_rvalue.md @@ -180,7 +180,7 @@ struct person { } ``` -Так мы получим нужно поведение за исключением того, что иногда move-конструктор вызовется лишний раз (когда делаем копию и вызываем move). Обычно это оптимизируется и не влияет на производительность, но при необходимости можно сделать разные перегрузки. +Так мы получим нужное поведение за исключением того, что иногда move-конструктор вызовется лишний раз (когда делаем копию и вызываем move). Обычно это оптимизируется и не влияет на производительность, но при необходимости можно сделать разные перегрузки. **Не стоит использовать move там, где он не нужен**: From 3283d2b3ccef00d90ed231cde122eb5bdbb29696 Mon Sep 17 00:00:00 2001 From: Ivan Kostin Date: Fri, 22 Aug 2025 00:09:30 +0300 Subject: [PATCH 06/12] Fold expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавил заметку про Fold expressions --- src/20_perfect_forwarding.md | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/20_perfect_forwarding.md b/src/20_perfect_forwarding.md index 3b34fda..b906604 100644 --- a/src/20_perfect_forwarding.md +++ b/src/20_perfect_forwarding.md @@ -271,3 +271,43 @@ void write(T0 const& arg0, Ts const& ...args) { write(args...); } ``` + +## Fold Expressions + +В **C++17** появилась фича **Fold Expressions**, позволяющая сделать свёртку по элементам пака. + +Мотивирующий пример: попробуем научиться проверять, есть ли тип (T) в паке. Так как пак в C++ имеет вид lazy массива (То есть мы имеем право получить только Head и Tail), очевидна следующая реализация: + +```c++ +template +struct have_type; + +template +struct have_type { + static constexpr bool value == std::is_same_v || + have_type::value; +// ^^^^^^^^^^^^^^^^^^^^^^^^ инстанс нового шаблона +}; + +template +struct have_type { + static constexpr bool value == std::is_same_v; +}; +``` + +Чем плоха такая реализация? Предположим в паке N элементов, тогда компилятор будет проводит N инстансов шаблона 'have_type'. Как можно это пофиксить? Вспомним, что `...` ставится там, где перечисляются элементы. В 'fold expressions' это и используется. Тогда прошлый пример будет выглядеть так: + +```c++ +template +struct have_type; + +template +struct have_type { + static constexpr bool value == std::is_same_v || ...; +}; +``` + +Здесь мы как бы раскрываем пак с операцией и проводим свёртку в какой-то init. Подробнее про 'fold expressions' на [cppreference](https://en.cppreference.com/w/cpp/language/fold.html). + +Чем это хорошо? 'Fold expressions' выигрывает у использования lazy массивов ассимптотически относительно инстанса шаблонов (Для N типов в паке происходит log(N) инстансов вместо N), поэтому там, где можно её заюзать, лучше это сделать. + From e1d568af987569797a9fce65fca76ba4f5f62394 Mon Sep 17 00:00:00 2001 From: Ivan Kostin Date: Fri, 22 Aug 2025 00:11:40 +0300 Subject: [PATCH 07/12] format upd --- src/20_perfect_forwarding.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/20_perfect_forwarding.md b/src/20_perfect_forwarding.md index b906604..b6eaf4f 100644 --- a/src/20_perfect_forwarding.md +++ b/src/20_perfect_forwarding.md @@ -295,7 +295,7 @@ struct have_type { }; ``` -Чем плоха такая реализация? Предположим в паке N элементов, тогда компилятор будет проводит N инстансов шаблона 'have_type'. Как можно это пофиксить? Вспомним, что `...` ставится там, где перечисляются элементы. В 'fold expressions' это и используется. Тогда прошлый пример будет выглядеть так: +Чем плоха такая реализация? Предположим в паке N элементов, тогда компилятор будет проводит N инстансов шаблона `have_type`. Как можно это пофиксить? Вспомним, что `...` ставится там, где перечисляются элементы. В `fold expressions` это и используется. Тогда прошлый пример будет выглядеть так: ```c++ template @@ -309,5 +309,5 @@ struct have_type { Здесь мы как бы раскрываем пак с операцией и проводим свёртку в какой-то init. Подробнее про 'fold expressions' на [cppreference](https://en.cppreference.com/w/cpp/language/fold.html). -Чем это хорошо? 'Fold expressions' выигрывает у использования lazy массивов ассимптотически относительно инстанса шаблонов (Для N типов в паке происходит log(N) инстансов вместо N), поэтому там, где можно её заюзать, лучше это сделать. +Чем это хорошо? `Fold expressions` выигрывает у использования lazy массивов ассимптотически относительно инстанса шаблонов (Для N типов в паке происходит log(N) инстансов вместо N), поэтому там, где можно её заюзать, лучше это сделать. From 18e998d96162482bfc68cc9174ba0b344f5d383f Mon Sep 17 00:00:00 2001 From: Ivan Kostin Date: Fri, 22 Aug 2025 16:37:44 +0300 Subject: [PATCH 08/12] 20_perfect_forwarding.md Fols Expressions 2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Почистил заметку --- src/20_perfect_forwarding.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/20_perfect_forwarding.md b/src/20_perfect_forwarding.md index b6eaf4f..775b9ce 100644 --- a/src/20_perfect_forwarding.md +++ b/src/20_perfect_forwarding.md @@ -274,9 +274,9 @@ void write(T0 const& arg0, Ts const& ...args) { ## Fold Expressions -В **C++17** появилась фича **Fold Expressions**, позволяющая сделать свёртку по элементам пака. +В **C++17** появилась фича **Fold Expressions**, позволяющая сделать свёртку по элементам пака относительно бинарного оператора. -Мотивирующий пример: попробуем научиться проверять, есть ли тип (T) в паке. Так как пак в C++ имеет вид lazy массива (То есть мы имеем право получить только Head и Tail), очевидна следующая реализация: +Мотивирующий пример: попробуем научиться проверять, есть ли тип (T) в паке. Так как пак в C++ имеет вид lazy array (То есть мы имеем право получить только Head и Tail), очевидна следующая реализация: ```c++ template @@ -284,18 +284,18 @@ struct have_type; template struct have_type { - static constexpr bool value == std::is_same_v || + static constexpr bool value = std::is_same_v || have_type::value; // ^^^^^^^^^^^^^^^^^^^^^^^^ инстанс нового шаблона }; template struct have_type { - static constexpr bool value == std::is_same_v; + static constexpr bool value = std::is_same_v; }; ``` -Чем плоха такая реализация? Предположим в паке N элементов, тогда компилятор будет проводит N инстансов шаблона `have_type`. Как можно это пофиксить? Вспомним, что `...` ставится там, где перечисляются элементы. В `fold expressions` это и используется. Тогда прошлый пример будет выглядеть так: +Чем плоха такая реализация? Предположим в паке N элементов, тогда компилятор будет проводить N инстансов шаблона `have_type`. Как можно это пофиксить? Вспомним, что `...` ставится там, где перечисляются элементы. В Fold expressions это и используется. Тогда предыдущий пример можно переписать так: ```c++ template @@ -303,11 +303,13 @@ struct have_type; template struct have_type { - static constexpr bool value == std::is_same_v || ...; + static constexpr bool value = std::is_same_v || ...; }; ``` -Здесь мы как бы раскрываем пак с операцией и проводим свёртку в какой-то init. Подробнее про 'fold expressions' на [cppreference](https://en.cppreference.com/w/cpp/language/fold.html). - -Чем это хорошо? `Fold expressions` выигрывает у использования lazy массивов ассимптотически относительно инстанса шаблонов (Для N типов в паке происходит log(N) инстансов вместо N), поэтому там, где можно её заюзать, лучше это сделать. +Чем это хорошо? +- Нет рекурсии. +- Быстрее compile time, потому что меньше инстансов и у компилятора есть возможность делать оптимизации. +- Легче читается и меньше кода. +Подробнее про Fold expressions на [cppreference](https://en.cppreference.com/w/cpp/language/fold.html). From e0a43d1b524eef386cb4868007eac04e0dc42163 Mon Sep 17 00:00:00 2001 From: Ivan Kostin Date: Fri, 22 Aug 2025 16:55:30 +0300 Subject: [PATCH 09/12] Update 21_decltype_auto_nullptr.md. Grammar 5 --- src/21_decltype_auto_nullptr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/21_decltype_auto_nullptr.md b/src/21_decltype_auto_nullptr.md index 8bd8698..ea1fcfb 100644 --- a/src/21_decltype_auto_nullptr.md +++ b/src/21_decltype_auto_nullptr.md @@ -169,7 +169,7 @@ auto f(bool flag) { // COMPILE ERROR ```c++ struct nullptr_t { template - opeartor T*() const { + operator T*() const { return 0; } } From 42e998764549cad2b552a2e6dca520b15c67e24a Mon Sep 17 00:00:00 2001 From: Ivan Kostin Date: Fri, 22 Aug 2025 19:37:55 +0300 Subject: [PATCH 10/12] Update 22_lambdas_type_erasure.md. example upd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Из-за того, что функции объявлены в одном порядке, а используются в другом возникает путаница, что из них считать первым вариантом. Решил чётко разграничить --- src/22_lambdas_type_erasure.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/22_lambdas_type_erasure.md b/src/22_lambdas_type_erasure.md index 4da5b05..56c79ff 100644 --- a/src/22_lambdas_type_erasure.md +++ b/src/22_lambdas_type_erasure.md @@ -25,9 +25,9 @@ struct less { // можно сделать класс со static-функцией, но это не даёт выигрыша // так как ABI устроен так, что пустые структуры можно не копировать -int foo (vector& v) { - std::sort(v.begin(), v.end(), less()); - std::sort(v.begin(), v.end(), &int_less); +void foo (vector& v) { + std::sort(v.begin(), v.end(), less()); // 1. + std::sort(v.begin(), v.end(), &int_less); // 2. } ``` From 989968189dcb792bb7c4c2516920f473948ce3f7 Mon Sep 17 00:00:00 2001 From: Ivan Kostin Date: Fri, 22 Aug 2025 20:33:04 +0300 Subject: [PATCH 11/12] Update 22_lambdas_type_erasure.md. Grammar 6 --- src/22_lambdas_type_erasure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/22_lambdas_type_erasure.md b/src/22_lambdas_type_erasure.md index 56c79ff..e60ea7e 100644 --- a/src/22_lambdas_type_erasure.md +++ b/src/22_lambdas_type_erasure.md @@ -375,7 +375,7 @@ auto bind(F f, Args... args) { } ``` -На смом деле, там [немного сложнее](https://en.cppreference.com/w/cpp/utility/functional/bind): можно закреплять конкретные аргументы, а остальные принимать при вызове. +На самом деле, там [немного сложнее](https://en.cppreference.com/w/cpp/utility/functional/bind): можно закреплять конкретные аргументы, а остальные принимать при вызове. ### Рекурсивный вызов лямбд From 100c61715fc78380d52d849e8742da1abcc648e2 Mon Sep 17 00:00:00 2001 From: Ivan Date: Wed, 17 Sep 2025 15:53:12 +0300 Subject: [PATCH 12/12] fixes dub1 --- src/10_libraries.md | 2 +- src/16_namespaces_using_adl.md | 2 +- src/20_perfect_forwarding.md | 37 ++++++++++++++++++++++------------ 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/10_libraries.md b/src/10_libraries.md index 4945f37..0543497 100644 --- a/src/10_libraries.md +++ b/src/10_libraries.md @@ -24,7 +24,7 @@ int main(){ } ``` ```c++ -// five +// five.cpp #include int sum(int a, int b); diff --git a/src/16_namespaces_using_adl.md b/src/16_namespaces_using_adl.md index 4acc83a..5c45346 100644 --- a/src/16_namespaces_using_adl.md +++ b/src/16_namespaces_using_adl.md @@ -298,7 +298,7 @@ int main() { ```c++ namespace my_lib { struct big_integer {}; - void swap(big_integer&, big_integer&) {// ... //} + void swap(big_integer&, big_integer&) {/* ... */} } template diff --git a/src/20_perfect_forwarding.md b/src/20_perfect_forwarding.md index 775b9ce..060bd8e 100644 --- a/src/20_perfect_forwarding.md +++ b/src/20_perfect_forwarding.md @@ -276,40 +276,51 @@ void write(T0 const& arg0, Ts const& ...args) { В **C++17** появилась фича **Fold Expressions**, позволяющая сделать свёртку по элементам пака относительно бинарного оператора. -Мотивирующий пример: попробуем научиться проверять, есть ли тип (T) в паке. Так как пак в C++ имеет вид lazy array (То есть мы имеем право получить только Head и Tail), очевидна следующая реализация: +Рассмотрим простой пример свёртки: + +```c++ +int sum(int... args) { + return (args + ...); +} + +int foo() { + return sum(1, 2, 3, 4); +} + +//Вызов функции sum(1, 2, 3, 4) раскроется в ((1 + 2) + 3) + 4 +``` + +Мотивирующий пример для метапрограммирования: попробуем научиться проверять, есть ли тип (`T`) в паке. Так как в шаблонном паке мы не можем получить доступ к конкретному элементу (до C++26), а может только отделять первые несколько элементов `Head` и все остальные `Tail`, очевидна следующая реализация: ```c++ template -struct have_type; +struct has_type; template -struct have_type { +struct has_type { static constexpr bool value = std::is_same_v || - have_type::value; -// ^^^^^^^^^^^^^^^^^^^^^^^^ инстанс нового шаблона + has_type::value; +// ^^^^^^^^^^^^^^^^^^^^^^^^ инстанс специализации шаблона }; template -struct have_type { +struct has_type { static constexpr bool value = std::is_same_v; }; ``` -Чем плоха такая реализация? Предположим в паке N элементов, тогда компилятор будет проводить N инстансов шаблона `have_type`. Как можно это пофиксить? Вспомним, что `...` ставится там, где перечисляются элементы. В Fold expressions это и используется. Тогда предыдущий пример можно переписать так: +Чем плоха такая реализация? Предположим в паке `N` элементов, тогда компилятор будет проводить `N` инстансов шаблона `has_type`. Как можно это пофиксить? Вспомним, что `...` ставится там, где перечисляются элементы. В `Fold expressions` это и используется. Тогда предыдущий пример можно переписать так: ```c++ -template -struct have_type; - template -struct have_type { - static constexpr bool value = std::is_same_v || ...; +struct has_type { + static constexpr bool value = (std::is_same_v || ...); }; ``` Чем это хорошо? - Нет рекурсии. -- Быстрее compile time, потому что меньше инстансов и у компилятора есть возможность делать оптимизации. +- Быстрее время компиляции, потому что меньше инстансов и у компилятора есть возможность делать оптимизации. - Легче читается и меньше кода. Подробнее про Fold expressions на [cppreference](https://en.cppreference.com/w/cpp/language/fold.html).