Skip to content

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

@qruk

Description

@qruk

Проблема

При проектировании систем без исключений возникает необходимость возвращать ошибки через 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::
  • Универсальность: работает не только с объектами, но и с глобальными функциями

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions