ideas icon indicating copy to clipboard operation
ideas copied to clipboard

Атрибут `breakeval` для расширения short-circuit evaluation

Open qruk opened this issue 2 months ago • 0 comments

Проблема

При проектировании систем без исключений возникает необходимость возвращать ошибки через std::error_code, std::optional<T>, std::expected<T>, ..., что приводит к массовым проверкам возвращаемых значений:

  1. Early-return (многословно):
if (auto ec = fn1(arg1)) {
    // обработка
    return ec;
}
if (auto ec = fn2(arg2, arg3)) {
    // обработка  
    return ec;
}
  1. Явное присвоение (бойлерплейт):
std::error_code ec;
ec || (ec = fn1(arg1));
ec || (ec = fn2(arg2, arg3));
return ec;
  1. Перегрузки с передачей error_code (калечит интерфейсы функций):
std::error_code ec;
ec || fn1(arg1, ec);
ec || fn2(arg2, arg3, ec);
return ec;
  1. Кастомные классы с ленивыми вычислениями (синтаксический мусор):
result_chain<std::error_code> ec;
ec || [&]{ return fn1(arg1); };
ec || [&]{ return fn2(arg2, arg3); };
return ec.value();

При этом

  • Перегрузки операторов ||, && не поддерживают short-circuit evaluation
  • Макросы мало кто любит, к тому же их нельзя вынести в модуль

Предложение

Ввести атрибут/спецификатор breakeval, который:

  • Применяется к функции/методу
  • Заставляет компилятор всегда организовывать вычисление самого левого аргумента первым
  • Останавливает дальнейшие вычисления аргументов функции, если результат вычисления первого аргумента false
  • Ошибка компиляции, если тип первого параметра не приводим к bool

Синтаксис:

class Class {
    bool operator bool() const { return !ec_; }
    
    breakeval Class& operator||(error_type& ec);  // при *this == false вычисления останавливаются
};

breakeval Class& operator||(Class& c, error_type& ec);  // при c == false вычисления останавливаются

Области применения

1. Логгирование

// Вместо макросов:
bool logger::operator bool() { return log_level_ < current_log_level(); };

template<typename Args...>
breakeval void logger::operator()(Args&&...) { ... }

...

log::info ("Something i need to log", "another data");
log::warn ("Attention! Its optional evaluation");

2. Цепочки вычислений

std::error_code ec = make_error_code(  );
// Аргументы оператора || не будут вычисляться благодаря breakeval
ec || large_arg_calc() || large_arg_calc2();
return ec;

// value_or с ленивым вычислением
// (не будет вычислять large_default_value_creation() в позитивном сценарии)
get_optional_value().value_or( large_default_value_creation() );

3. Работа с потенциально инвалидными объектами

std::ofstream os("some_path");
os << get_large_title() << get_large_body() << get_some_other_data();
// Вычисления останавливаются при первой ошибке потока

Что дает?

  • Читаемость: можно будет упразднить синтаксический мусор проверок
  • Обратная совместимость: введение нового атрибута не сломает существующий код
  • Эффективность: позволяет исключать ненужные вычисления, постепенно это можно будет внедрить и в std::
  • Универсальность: работает не только с объектами, но и с глобальными функциями

qruk avatar Nov 04 '25 23:11 qruk