# Атрибут [[assume]] "Есть некоторая вселенская несправедливость", — подумали в комитете стандартизации C++, — "мы так много всего в языке назначили быть неопределенным поведением, чтоб помочь компиляторам генерировать оптимальный код. Но не дали такую же стандартную возможность нашим пользователям — программистам!" Да, С++23 наконец-то дал простым пользователям инструмент целенаправленного **внедрения** неопределенного поведения в их код. Такой инструмент, правда, давно уже был и так, но специфичный для конкретного компилятора. C++23 же всего лишь стандартизировал его. Так что радуйтесь, никаких больше уродливых `__builtin_assume`! - Зачем вообще такая возможность существует?! — первый же вопрос, который возникает после прочтения абзаца выше. Неужели недостаточно ужасов самого языка, нужно еще пользователям позволить создавать новые?! На самом деле, конечно, причина есть: компиляторы глупые, быстрый и оптимальный код получить хочется, а на ассемблере писать не очень хочется. Хотя, конечно, разработчики ffmpeg с этим не согласятся — они поэтому целенаправленно делают ассемблерные вставки, не доверяя компиляторам С. Несмотря на то что мы говорим о C и C++, я позволю себе привести пример на Rust, поскольку считаю, что он наиболее ярко может продемонстрировать логику нововведения С++23. Возьмем достаточно простую функцию, которая выполняет семплирование отсортированной выборки: разбивает ее на группы равной величины и из каждой группы выбирает медианную величину ```Rust use std::num::NonZeroUsize; pub fn medians(data: &[f32], group: NonZeroUsize) -> Vec { let n = group.get(); data.chunks_exact(n) // разбиваем на группы по n, // последняя группа если в ней меньше n -- игнорируется .map(move |chunk| chunk[n/2]) // берем медиану .collect() // собираем результат } ``` Если мы [скомпилируем](https://godbolt.org/z/vYezzv9ba) эту функцию довольно старой версией Rustc 1.51 с opt-level=3, мы обнаружим, что код получился так себе 1. Мы видим в начале функции ``` sub rsp, 120 mov qword ptr [rsp + 96], rcx test rcx, rcx je .LBB4_33 ... .LBB4_33: ... call qword ptr [rip + core::panicking::panic_fmt::hcd56f7f635f62c74@GOTPCREL] ud2 ``` Это проверка что `n` не ноль. Но мы же и так знаем что `n` не ноль ­— это четко указано в типе входного параметра! 2. При обработке каждой группы мы находим ``` shr rdi cmp rdi, r15 jae .LBB4_27 ... .LBB4_27: lea rdx, [rip + .L__unnamed_4] mov rsi, r15 call qword ptr [rip + core::panicking::panic_bounds_check::h16537cfb53a1364b@GOTPCREL] ``` Каждый раз проверяется что индекс `n/2` в границах группы. Но ведь это всегда так! Очень бы хотелось донести до компилятора такие очевидные факты. Собственно `[[assume(condition)]]` для того в C++23 и добавили. Если компилятор не смог догадаться до чего-то самостоятельно и сгенерировать оптимальный код, мы теперь можем ему подсказать... Так c GCC14 и C++26 та же самая функция (используя безопасные методы, как в Rust) ```C++ struct NonZero { public: explicit NonZero(size_t v) : value { v > 0 ? v : throw std::runtime_error("Zero value") } {} size_t get() const { return value; } private: size_t value; }; template auto chunks_exact(std::span data, size_t n) { if (n == 0) { throw std::runtime_error("zero chunk len"); } return data.subspan(0, data.size() - data.size() % n) | std::views::chunk(n) | std::views::transform([](auto chunk){ return std::span(&chunk.front(), chunk.size()); }); // remap into spans } __attribute__((noinline)) std::vector medians(std::span data, NonZero group) { size_t n = group.get(); // [[assume(n>0)]]; return chunks_exact(data, n) | std::views::transform([n](auto chunk) { return chunk.at(n/2); }) | std::ranges::to(); } ``` также компилируется со всеми ненужными проверками. Но стоит нам только лишь добавить `[[assume(n>0)]]`, как ситуация [меняется](https://godbolt.org/z/Yvez1WjeK) и все избыточные проверки на ноль и на границы групп могут быть успешно выброшены компилятором! --- Но что если мы подсказали неправильно? Неопределенное поведение, конечно же! Из ложной посылки следует что угодно. А если мы подсказывали правильно, но только на допустимом множестве входных данных? Отлично, все хорошо, только не забудьте включить в документацию упоминание неопределенного поведения на недопустимом входе. Но прежде чем начинать пользоваться такой замечательной возможностью языка, стоит понимать: 1. Правильная подсказка ничего не гарантирует. А ложная невероятно опасна 2. Новые версии компиляторов и сами могут догадаться. Так, например, rustc 1.80 на рассмотренном примере [оптимизирует](https://godbolt.org/z/c5Kc9hP8r) уже все как надо.