Модульна бібліотека для логування в C++23

Сьогодні на порядку денному — очевидний, але часто не використовуваний підхід: Негайно Викликані Лямбда Вирази (IILE).

Чому важливий const?

Незмінність є ключовим принципом для надійного і зрозумілого коду. Використання const, де це можливо, допомагає компілятору виявляти помилки та чітко вказує на намір програміста. Коли інша людина читає ваш код і бачить const, це значно знижує її когнітивне навантаження — "немає потреби відслідковувати зміни цієї змінної — все ясно". Важливість правильного використання const є основою багатьох хороших практик у C++.

Проблема

У простих випадках ініціалізація констант не викликає труднощів:

const int cmaxPlayers = 100;
const double c
scale = getScaleFactor() * 1.5;
const bool cenabled = check() || FORCEENABLE;
const int c_healthModifier = bHealing ? 20 : 0;

Але що робити, коли обчислення значення для константи вимагає кілька кроків, тимчасових змінних, циклів або умов?

float ccalculatedDamage = getBaseDamageValue();
if (targetAimed(calculatedDamage)) {
for (int i = 0; i < c
effectCount; ++i) {
calculatedDamage += getBonusDamage(i);
}
}

Традиційні підходи — винесення логіки в окрему функцію або зняття const — не завжди ідеальні. Винесення в окрему функцію може бути надмірним, якщо логіка використовується лише один раз. Зняття const знижує безпеку і виразність коду.

Негайно Викликаний Лямбда Вираз (IILE)

У таких випадках на допомогу приходить Негайно Викликаний Лямбда Вираз (IILE). Ми визначаємо лямбда-функцію, яка інкапсулює всю складну ініціалізаційну логіку, і негайно її викликаємо. Результат цього виклику присвоюється нашій константі.

Ось як це працює:

const auto myLambda = { return 13; }();

Усе залежить від дужок 🤪 Останні дужки () після лямбди — це негайний виклик, який змушує лямбду виконуватися одразу після її визначення. Це дозволяє ініціалізувати складний об'єкт та зберегти його незмінність:

const auto ccalculatedDamage = & {
float tempDamage = getBaseDamageValue();
if (targetAimed(tempDamage)) {
for (int i = 0; i < c
effectCount; ++i) {
tempDamage += getBonusDamage(i);
}
}
return tempDamage;
}();

Переваги IILE для ініціалізації

🟢 Інкапсуляція — Уся ініціалізаційна логіка зібрана в одному місці.
🟢 Локальність — Тимчасові змінні, що використовуються для обчислення, не впливають на зовнішнє середовище.
🟢 Правильність const — Можливість оголосити змінну як const (або навіть constexpr, якщо лямбда відповідає вимогам), навіть якщо її обчислення складається з кількох кроків.
🟢 Чистота коду — Виключає необхідність створювати одноразові функції.

Альтернативний синтаксис (C++17)

В C++17 можна використовувати std::invoke, хоча для IILE прямий виклик () зазвичай є кращим і зрозумілішим:

include

// ...
const auto c_anotherConstant = std::invoke([] {
// ... logic ...
return 13;
});

Джерела

🔗 ES.28: Використовуйте лямбди для складної ініціалізації, особливо для const змінних

Перекладено з: A modular logging library for C++23