У попередній статті ми вивчили вказівники на функції і callback-патерн. Це відкрило нам можливість передавати функцію як аргумент — наприклад, стратегію порівняння у функцію сортування. Але уявіть таку ситуацію: вам потрібно знайти перший рядок у масиві, що містить підрядок "nut". Ви пишете функцію:
#include <iostream>
#include <algorithm>
// Функція визначена в глобальній області — хоча потрібна лише раз і лише тут
bool containsNut(const char* str)
{
// Пошук підрядка через цикл — спрощений варіант
int i = 0;
int nutLen = 3; // "nut"
const char* target = "nut";
while (str[i] != '\0')
{
if (str[i] == target[0] && str[i + 1] == target[1] && str[i + 2] == target[2])
return true;
++i;
}
return false;
}
int main()
{
const char* fruits[] = { "apple", "banana", "walnut", "lemon" };
return 0;
}
Зверніть на проблеми цього підходу:
containsNut визначена в глобальній області — хоча вона потрібна лише в одному місці.Саме для вирішення цих проблем у стандарті C++11 з'явилися лямбда-вирази — синтаксис для визначення анонімної функції прямо в місці її використання.
std::function, auto і загального розуміння callback-патерну є обов'язковими.Загальна форма лямбда-виразу:
[ capture ] ( parameters ) -> ReturnType { body }
| Частина | Обов'язкова? | Призначення |
|---|---|---|
[ capture ] | ✅ Так (може бути порожньою []) | Захоплення зовнішніх змінних (стаття 26) |
( parameters ) | ❌ Можна опустити, якщо параметрів немає | Параметри функції |
-> ReturnType | ❌ Опціонально | Явний тип повернення (trailing return type) |
{ body } | ✅ Так | Тіло функції |
Найпростіша можлива лямбда — порожня, без параметрів і без тіла:
[]() {}; // мінімальний синтаксис: порожній capture, порожні параметри, порожнє тіло
Більш практичний приклад — лямбда, що приймає два числа і повертає їхню суму:
#include <iostream>
int main()
{
// Лямбда визначається і одразу зберігається у змінній
auto add = [](int a, int b)
{
return a + b; // тип повернення виводиться автоматично: int
};
std::cout << add(3, 5) << '\n'; // 8
std::cout << add(10, 7) << '\n'; // 17
return 0;
}
Розбір: auto add = [](int a, int b) { return a + b; }; — читається: «add — це лямбда без capture, що приймає два int і повертає їхню суму». Тип add компілятор виводить сам — він є унікальним і є внутрішнім для компілятора. Саме тому ми використовуємо auto.
Повернімося до задачі пошуку підрядка. Порівняємо два підходи поруч:
#include <iostream>
// Визначена в глобальній області, далеко від місця використання
bool isLong(const char* str)
{
int len = 0;
while (str[len] != '\0')
++len;
return len > 5;
}
int main()
{
const char* words[] = { "hi", "hello", "morning", "bye" };
// ... 50 рядків коду між визначенням і використанням ...
for (int i = 0; i < 4; ++i)
{
if (isLong(words[i]))
std::cout << words[i] << '\n';
}
return 0;
}
#include <iostream>
int main()
{
const char* words[] = { "hi", "hello", "morning", "bye" };
// Лямбда поруч з місцем використання — одразу зрозуміло навіщо
auto isLong = [](const char* str)
{
int len = 0;
while (str[len] != '\0')
++len;
return len > 5;
};
for (int i = 0; i < 4; ++i)
{
if (isLong(words[i]))
std::cout << words[i] << '\n';
}
return 0;
}
Переваги варіанту з лямбдою:
isLong — це локальна допоміжна логіка.Лямбда не зобов'язана зберігатися у змінній. Її можна передати прямо у виклик функції як аргумент:
#include <iostream>
void forEach(int* array, int size, void (*action)(int))
{
for (int i = 0; i < size; ++i)
action(array[i]);
}
int main()
{
int numbers[] = { 1, 2, 3, 4, 5 };
// Лямбда прямо у виклику — без імені, без окремого визначення
forEach(numbers, 5, [](int x)
{
std::cout << x * x << ' ';
});
std::cout << '\n'; // 1 4 9 16 25
return 0;
}
Коли іменувати лямбду, а коли — ні? Якщо лямбда коротка і її сенс очевидний із контексту — передавайте прямо у виклик. Якщо лямбда складна або використовується кілька разів — збережіть в іменованій змінній:
// ✅ Коротка і зрозуміла — прямо у виклику
forEach(numbers, 5, [](int x) { std::cout << x << ' '; });
// ✅ Складна або використовується кілька разів — іменована
auto isPositiveAndEven = [](int x)
{
return (x > 0) && (x % 2 == 0);
};
// Використовуємо isPositiveAndEven кілька разів...
auto?У C++ кожна лямбда має унікальний, генерований компілятором тип, який ми не можемо написати вручну. Два однакові за виглядом лямбда-вирази мають різні типи. Саме тому для зберігання лямбди у змінній єдиний природний вибір — auto:
#include <iostream>
int main()
{
auto lambdaA = [](int x) { return x + 1; };
auto lambdaB = [](int x) { return x + 1; }; // ТОЙ САМИЙ ВИГЛЯД — але інший тип!
// lambdaA = lambdaB; // ❌ Помилка: різні типи, хоч і однакова сигнатура
std::cout << lambdaA(5) << '\n'; // 6
std::cout << lambdaB(5) << '\n'; // 6
return 0;
}
operator(). Цим пояснюється унікальність типу: компілятор генерує унікальний клас для кожної лямбди.#include <iostream>
#include <functional>
int main()
{
// Спосіб 1: auto — зберігає реальний тип, без накладних витрат (рекомендовано)
auto addA = [](double a, double b) { return a + b; };
// Спосіб 2: std::function — гнучко, але є накладні витрати на виклик
std::function<double(double, double)> addB = [](double a, double b) { return a + b; };
// Спосіб 3: вказівник на функцію — лише якщо лямбда нічого не захоплює
double (*addC)(double, double) = [](double a, double b) { return a + b; };
std::cout << addA(1.5, 2.5) << '\n'; // 4
std::cout << addB(3.0, 1.0) << '\n'; // 4
std::cout << addC(2.0, 2.0) << '\n'; // 4
return 0;
}
Коли що використовувати:
| Ситуація | Рекомендація |
|---|---|
| Локальна змінна | auto |
| Параметр функції (будь-який callable) | std::function<R(Args...)> або шаблонний параметр |
| Лямбда без capture → параметр у C-API | вказівник на функцію |
std::function або auto.std::functionКоли функція приймає callable-параметр і ви хочете дозволити передавати як звичайні функції, так і лямбди — використовуйте std::function:
#include <iostream>
#include <functional>
// Функція, що викликає fn задану кількість разів
void repeat(int count, const std::function<void(int)>& fn)
{
for (int i = 0; i < count; ++i)
fn(i);
}
int main()
{
// Передаємо лямбду прямо у виклик
repeat(4, [](int i)
{
std::cout << "Крок " << i << '\n';
});
return 0;
}
autoПочинаючи з C++14, параметри лямбди можуть мати тип auto. Така лямбда називається узагальненою (generic lambda) — вона автоматично адаптується до типу переданого аргументу:
#include <iostream>
int main()
{
// Узагальнена лямбда: auto-параметр — працює з будь-яким типом
auto printValue = [](const auto& value)
{
std::cout << value << '\n';
};
printValue(42); // виводить: 42 (auto деduced → int)
printValue(3.14); // виводить: 3.14 (auto deduced → double)
printValue("Hello"); // виводить: Hello (auto deduced → const char*)
return 0;
}
Важливий нюанс: для кожного унікального типу аргументу компілятор генерує окремий екземпляр лямбди. Це схоже на шаблони функцій. Саме тому у наступному прикладі callCount не є спільним між версіями для int і const char*:
#include <iostream>
int main()
{
auto trackCalls = [](auto value)
{
static int callCount = 0; // окремий для кожного типу!
std::cout << callCount++ << ": " << value << '\n';
};
trackCalls("hello"); // 0: hello (екземпляр для const char*)
trackCalls("world"); // 1: world (той самий екземпляр)
trackCalls(10); // 0: 10 (новий екземпляр для int!)
trackCalls(20); // 1: 20 (екземпляр для int)
trackCalls("!"); // 2: ! (знову екземпляр для const char*)
return 0;
}
Дві версії лямбди (const char* і int) мають окремі callCount — адже компілятор генерує два різні класи.
Зазвичай компілятор виводить тип повернення лямбди автоматично з оператора return. Але якщо тіло лямбди містить кілька return з різними типами — це призведе до помилки:
// ❌ Помилка: перший return повертає int, другий — double
auto divide = [](int x, int y, bool isInteger)
{
if (isInteger)
return x / y; // int
else
return static_cast<double>(x) / y; // double ← несумісність!
};
Рішення — явно вказати тип повернення через trailing return type (->):
#include <iostream>
int main()
{
// Явно вказуємо -> double: обидва return тепер конвертуються в double
auto divide = [](int x, int y, bool isInteger) -> double
{
if (isInteger)
return x / y; // int → неявна конвертація в double
else
return static_cast<double>(x) / y; // double
};
std::cout << divide(7, 2, true) << '\n'; // 3 (цілочисельний поділ)
std::cout << divide(7, 2, false) << '\n'; // 3.5 (дійсний поділ)
return 0;
}
-> double після списку параметрів — це trailing return type, і він читається природно: «лямбда, що приймає int, int, bool і повертає double».
Для найпоширеніших операцій — порівняння, арифметика, логіка — стандартна бібліотека надає готові функтори у заголовку <functional>. Замість написання власної лямбди для сортування за спаданням можна скористатися std::greater:
#include <iostream>
#include <functional>
#include <algorithm>
int main()
{
int numbers[] = { 13, 90, 5, 40, 80, 99 };
const int SIZE = 6;
// Без STL-алгоритмів — сортуємо вручну для демонстрації
// std::sort(numbers, numbers + SIZE, std::greater<int>{}); // за спаданням
// Еквівалент через лямбду:
auto greaterThan = [](int a, int b) { return a > b; };
// Сортування методом бульбашки для демонстрації (not std::sort)
for (int i = 0; i < SIZE - 1; ++i)
{
for (int j = 0; j < SIZE - i - 1; ++j)
{
if (!greaterThan(numbers[j], numbers[j + 1]))
std::swap(numbers[j], numbers[j + 1]);
}
}
for (int i = 0; i < SIZE; ++i)
std::cout << numbers[i] << ' ';
std::cout << '\n';
return 0;
}
Зведення найкорисніших функторів STL:
| Функтор | Еквівалентна лямбда | Опис |
|---|---|---|
std::greater<T>{} | [](T a, T b){ return a > b; } | a > b |
std::less<T>{} | [](T a, T b){ return a < b; } | a < b |
std::equal_to<T>{} | [](T a, T b){ return a == b; } | a == b |
std::plus<T>{} | [](T a, T b){ return a + b; } | a + b |
std::negate<T>{} | [](T a){ return -a; } | -a |
Теорія закладена — час закріпити її через конкретні, ретельно розібрані приклади. Кожен із них вирішує реальну задачу і демонструє окремий аспект лямбда-виразів. Читайте покрокові пояснення: вони навмисно виписані детально.
countIf — підрахунок елементів за умовоюЗадача: функція countIf приймає масив, його розмір і предикат-лямбду. Вона повертає кількість елементів, для яких предикат повертає true. Це прямий аналог std::count_if з STL.
#include <iostream>
#include <functional>
// Приймає будь-який callable bool(int) через std::function
int countIf(int* array, int size, const std::function<bool(int)>& predicate)
{
int count = 0;
for (int i = 0; i < size; ++i)
{
if (predicate(array[i])) // виклик лямбди — такий самий, як виклик функції
++count;
}
return count;
}
int main()
{
int numbers[] = { -4, 7, 0, 12, -1, 3, 8, -9, 5, 6 };
const int SIZE = 10;
// Лямбда 1: парні числа
auto isEven = [](int n)
{
return n % 2 == 0;
};
// Лямбда 2: від'ємні числа
auto isNegative = [](int n)
{
return n < 0;
};
// Лямбда 3: у діапазоні [1, 9]
auto isInRange = [](int n)
{
return n >= 1 && n <= 9;
};
std::cout << "Парних: " << countIf(numbers, SIZE, isEven) << '\n';
std::cout << "Від'ємних: " << countIf(numbers, SIZE, isNegative) << '\n';
std::cout << "В діапазоні: " << countIf(numbers, SIZE, isInRange) << '\n';
// Можна передати лямбду прямо у виклик:
std::cout << "Нулів: " << countIf(numbers, SIZE, [](int n) { return n == 0; }) << '\n';
return 0;
}
Рядок 5. const std::function<bool(int)>& predicate — параметр типу «константне посилання на std::function, що приймає int і повертає bool``. Константне посилання (const&) тут є оптимізацією: std::function— важкий об'єкт, передавати його за значенням означало б зайве копіювання. Але нам читати непотрібно змінювати предикат — томуconst`.
Рядок 11. predicate(array[i]) — лямбда викликається так само, як звичайна функція. std::function «обгортає» лямбду і надає однаковий синтаксис виклику незалежно від реального типу callable.
Рядок 26–29. Лямбда isNegative — однорядкове тіло. Задля читабельності її можна було б написати в один рядок: auto isNegative = [](int n) { return n < 0; };. Обидва записи рівнозначні.
Рядок 47. [](int n) { return n == 0; } — лямбда прямо у виклику, без іменування. Виправдано: умова «одно рівне нулю» є простою і зрозумілою без назви.
transformArray — перетворення елементівЗадача: функція transformArray замінює кожен елемент масиву результатом застосування лямбди-трансформера. Операція — «in-place» (без виділення нового масиву).
#include <iostream>
#include <functional>
// Замінює кожен елемент результатом fn(element). In-place.
void transformArray(int* array, int size, const std::function<int(int)>& fn)
{
for (int i = 0; i < size; ++i)
array[i] = fn(array[i]); // Читаємо старе значення → передаємо в fn → записуємо результат
}
void printArray(int* array, int size)
{
for (int i = 0; i < size; ++i)
std::cout << array[i] << ' ';
std::cout << '\n';
}
int main()
{
int numbers[] = { 1, -2, 3, -4, 5, -6 };
const int SIZE = 6;
std::cout << "До: ";
printArray(numbers, SIZE);
// Трансформація 1: подвоїти кожен елемент
transformArray(numbers, SIZE, [](int x)
{
return x * 2;
});
std::cout << "Подвоїти: ";
printArray(numbers, SIZE);
// Трансформація 2: взяти абсолютне значення
// abs(x): якщо x < 0 → -x, інакше → x
transformArray(numbers, SIZE, [](int x)
{
return x < 0 ? -x : x;
});
std::cout << "Abs: ";
printArray(numbers, SIZE);
// Трансформація 3: звести в квадрат
transformArray(numbers, SIZE, [](int x)
{
return x * x;
});
std::cout << "Квадрат: ";
printArray(numbers, SIZE);
return 0;
}
Рядок 8. array[i] = fn(array[i]) — ця єдина, компактна рядок і є серцем transformArray. Зліва — запис за індексом i, справа — читання того ж елемента і передача його у fn. Порядок операцій: спочатку fn(array[i]) виконується до кінця і обчислюється нове значення, потім воно присвоюється в array[i]. Старе значення при цьому вже не потрібне — і це безпечно.
Рядок 38–41. x < 0 ? -x : x — тернарний оператор замість if. Для лямбди з єдиним виразом return це доречно: весь вираз читається як математична формула |x|.
Архітектурна ідея: зверніть, що після transformArray(numbers, SIZE, [](int x) { return x * 2; }) масив назавжди змінюється. Наступний виклик Abs вже бачить подвоєні значення — і це навмисне: ми демонструємо ланцюгове застосування трансформацій до одного масиву. В реальних програмах часто замість in-place трансформації копіюють масив, щоб зберегти оригінал.
Задача: реалізувати сортування бульбашкою (bubble sort), де критерій порівняння — лямбда. Це демонструє, що лямбда замінює іменований callback так само ефективно, але компактніше.
#include <iostream>
#include <functional>
#include <utility> // для std::swap
void bubbleSort(int* array, int size, const std::function<bool(int, int)>& shouldSwap)
{
for (int pass = 0; pass < size - 1; ++pass)
{
for (int i = 0; i < size - pass - 1; ++i)
{
// Запитуємо лямбду: чи треба міняти місцями array[i] і array[i+1]?
if (shouldSwap(array[i], array[i + 1]))
std::swap(array[i], array[i + 1]);
}
}
}
void printArray(int* array, int size)
{
for (int i = 0; i < size; ++i)
std::cout << array[i] << ' ';
std::cout << '\n';
}
int main()
{
int numbersA[] = { 64, 34, 25, 12, 22, 11, 90 };
int numbersB[] = { 64, 34, 25, 12, 22, 11, 90 }; // копія для другого сортування
const int SIZE = 7;
// Лямбда 1: за зростанням — міняти, якщо лівий БІЛЬШИЙ за правий
bubbleSort(numbersA, SIZE, [](int a, int b)
{
return a > b; // a зліва, b справа; якщо a > b — вони в неправильному порядку
});
std::cout << "За зростанням: ";
printArray(numbersA, SIZE);
// Лямбда 2: за спаданням — міняти, якщо лівий МЕНШИЙ за правий
bubbleSort(numbersB, SIZE, [](int a, int b)
{
return a < b; // якщо a < b — для спадного порядку вони стоять неправильно
});
std::cout << "За спаданням: ";
printArray(numbersB, SIZE);
return 0;
}
Рядок 5. const std::function<bool(int, int)>& shouldSwap — зверніть на ім'я параметра. Замість безликого compare або fn ми використовуємо shouldSwap — ім'я, що точно описує семантику: «чи треба міняти місцями?». Хороше ім'я callback-параметра є частиною документації функції.
Рядок 12. if (shouldSwap(array[i], array[i + 1])) — цей рядок нічого не знає про конкретну умову. bubbleSort повністю відокремлена від логіки сортування. Зверніть: перший аргумент — лівий (поточний) елемент, другий — правий (наступний). Саме тому:
a > b означає «лівий більший → він має стояти правіше → міняємо» → зростання.a < b означає «лівий менший → він має стояти правіше → міняємо» → спадання.Рядки 28–29. numbersA[] і numbersB[] — два окремі масиви з однаковими значеннями. Ми вже знаємо з попередніх статей: якщо передати один і той самий масив двічі, перше сортування змінить його, і друге сортуватиме вже змінений набір — тести були б невалідними.
Задача: написати функцію findExtreme, що знаходить «крайній» елемент масиву — мінімум чи максимум, залежно від переданої лямбди-порівнювача.
#include <iostream>
#include <functional>
// Знаходить "крайній" елемент масиву згідно з критерієм isBetter.
// isBetter(candidate, current) → true, якщо candidate є "кращим" за current
int findExtreme(int* array, int size, const std::function<bool(int, int)>& isBetter)
{
int extreme = array[0]; // починаємо з першого елемента як поточного "чемпіона"
for (int i = 1; i < size; ++i) // перебираємо решту, починаючи з другого
{
// Якщо поточний елемент "кращий" за досі знайдений — оновлюємо
if (isBetter(array[i], extreme))
extreme = array[i];
}
return extreme;
}
int main()
{
int numbers[] = { 3, -7, 14, 0, -2, 11, 8 };
const int SIZE = 7;
// Лямбда для мінімуму: candidate є "кращим", якщо він менший
auto isSmaller = [](int candidate, int current)
{
return candidate < current;
};
// Лямбда для максимуму: candidate є "кращим", якщо він більший
auto isLarger = [](int candidate, int current)
{
return candidate > current;
};
// Лямбда для "найближчого до нуля":
// candidate є "кращим", якщо його абсолютне значення менше
auto isCloserToZero = [](int candidate, int current)
{
int absCand = candidate < 0 ? -candidate : candidate;
int absCurr = current < 0 ? -current : current;
return absCand < absCurr;
};
std::cout << "Мінімум: " << findExtreme(numbers, SIZE, isSmaller) << '\n';
std::cout << "Максимум: " << findExtreme(numbers, SIZE, isLarger) << '\n';
std::cout << "Ближче до нуля: " << findExtreme(numbers, SIZE, isCloserToZero) << '\n';
return 0;
}
Концепція «чемпіона» (рядок 8). int extreme = array[0] — ми починаємо з першого елемента і умовно вважаємо його «поки що найкращим». Потім у циклі кожен наступний елемент порівнюється з поточним «чемпіоном». Якщо новий елемент «кращий» (за критерієм isBetter) — він стає новим чемпіоном. Цей патерн є стандартним для всіх алгоритмів «знайти крайній».
Рядок 13. isBetter(array[i], extreme) — перший аргумент завжди поточний кандидат (перевіряємо), другий — поточний чемпіон (з яким порівнюємо). Це конвенція: лямбда-порівнювач відповідає на питання «чи є перший аргумент кращим за другий?»
Рядки 38–42. Лямбда isCloserToZero є найцікавішою: вона обчислює абсолютне значення двох чисел вручну (через тернарний оператор) і порівнює їх. Зверніть — це кілька рядків тіла лямбди, і це абсолютно нормально. Лямбда — це повноцінна функція, і вона може містити будь-яку кількість рядків.
Задача: написати функцію валідації числа, що перевіряє одразу кілька умов. Це демонструє, що лямбда — не завжди «один рядок».
#include <iostream>
#include <functional>
// Перевіряє число за набором правил. Якщо хоч одне порушене — повертає false.
bool validate(int value, const std::function<bool(int)>* rules, int ruleCount)
{
for (int i = 0; i < ruleCount; ++i)
{
if (!rules[i](value)) // якщо i-те правило повернуло false
{
std::cout << " Правило #" << i + 1 << " порушено!\n";
return false; // зупиняємось при першому порушенні
}
}
return true; // усі правила дотримані
}
int main()
{
// Масив правил-лямбд — кожна перевіряє одну умову
std::function<bool(int)> rules[] =
{
// Правило 1: не від'ємне
[](int n)
{
return n >= 0;
},
// Правило 2: менше 100
[](int n)
{
return n < 100;
},
// Правило 3: парне
[](int n)
{
return n % 2 == 0;
},
// Правило 4: не кратне 7
[](int n)
{
return n % 7 != 0;
}
};
const int RULE_COUNT = 4;
int testValues[] = { 42, -5, 56, 100, 14 };
for (int i = 0; i < 5; ++i)
{
std::cout << "Перевірка " << testValues[i] << ":\n";
if (validate(testValues[i], rules, RULE_COUNT))
std::cout << " ✅ Валідне\n";
else
std::cout << " ❌ Невалідне\n";
}
return 0;
}
Рядки 21–46. std::function<bool(int)> rules[] — масив лямбд. Кожен елемент масиву є окремою функцією зі своєю логікою. Ініціалізуємо масив як звичайний масив, лише замість цілих чисел — лямбди. Зверніть: для масиву лямбд потрібен std::function, а не auto, бо всі елементи масиву мають бути одного типу, а auto генерує різні типи для різних лямбд.
Рядок 9. if (!rules[i](value)) — rules[i] звертається до i-го елемента масиву (який є std::function), (value) викликає його з аргументом. ! інвертує результат: якщо правило каже «некоректно» (false), ми входимо у тіло if.
Рядок 12. return false після першого порушеного правила — «швидка відмова» (fail fast). Немає сенсу перевіряти решту правил, якщо вже порушено перше. Це економить час.
Ключова ідея. Масив лямбд rules[] — це декларативний спосіб задати набір умов. Замість великого if (n >= 0 && n < 100 && n % 2 == 0 && n % 7 != 0) ми маємо чотири окремі, іменовані-коментарями, перевірки, кожну з яких легко додати, видалити або замінити незалежно від інших.
Рівень 1 — Базовий
Завдання 1. Напишіть лямбду square, що зводить число в квадрат. Збережіть її у auto-змінній. Викличте з аргументами 3, 5, 7 і виведіть результати.
Завдання 2. Напишіть лямбду isPositive, що повертає true, якщо число більше нуля. Використайте її у функції forEach (або власному циклі) для виводу лише позитивних чисел з масиву {-3, 1, -1, 4, 0, 7, -2}.
Завдання 3. Чому наступний код не компілюється? Виправте його двома способами:
auto classify = [](int x) {
if (x >= 0)
return x; // int
else
return -1.0 * x; // double — конфлікт!
};
Рівень 2 — Логіка
Завдання 4. Напишіть функцію int countIf(int* array, int size, std::function<bool(int)> predicate), що підраховує кількість елементів, для яких predicate повертає true. Протестуйте з лямбдами:
Завдання 5. Реалізуйте функцію void transform(int* array, int size, std::function<int(int)> fn), що замінює кожен елемент результатом fn(element). Протестуйте з лямбдами:
Завдання 6. Напишіть узагальнену лямбду printPair, що приймає два const auto& параметри і виводить їх у форматі "[a, b]". Протестуйте з парами: (int, int), (int, const char*), (double, double).
Рівень 3 — Архітектура
Завдання 7. Реалізуйте систему «конвеєра трансформацій» через масив std::function:
std::function<int(int)> pipeline[] = {
[](int x) { return x * 2; }, // подвоїти
[](int x) { return x + 10; }, // додати 10
[](int x) { return x * x; } // звести в квадрат
};
Напишіть функцію int applyPipeline(int value, std::function<int(int)>* steps, int count), що послідовно застосовує кожен крок. Протестуйте для числа 3: 3 → 6 → 16 → 256.
Синтаксис
[capture](params) -> Type { body }. Capture і params можуть бути порожніми. -> Type опціональний, якщо тип виводиться однозначно.Тип і зберігання
auto (локально) або std::function<R(Args...)> (як параметр функції).auto-параметри (C++14)
[](const auto& a, const auto& b) — узагальнена лямбда. Компілятор генерує окремий екземпляр для кожного типу.Коли не лямбда
std::greater, std::less тощо.У наступній статті ми розглянемо захоплення лямбди (lambda captures) — механізм, що дозволяє лямбді «бачити» змінні з навколишнього контексту, і саме тут лямбди демонструють всю свою справжню потужність.
Вказівники на функції
Опануйте вказівники на функції у C++ — механізм, що лежить в основі callback-патернів, функціонального програмування та гнучкого проектування. Синтаксис, псевдоніми типів, std::function та практичні кейси.
Лямбда-захоплення
Вивчіть механізм захоплення лямбда-виразів у C++ — захоплення за значенням та за посиланням, ключове слово mutable, захоплення за замовчуванням, визначення нових змінних у capture-списку та поширені пастки.