В практической ситуации нужно заранее связать первые аргументы вызываемой функции без громоздкого std::bind: какой стандартный механизм C++20 выбрать?
Выберите std::bind_front. Он создаёт вызываемый объект, сохраняющий функцию и заданные первые аргументы, а последующие аргументы принимает при вызове в обычном порядке.
В отличие от std::bind, механизм не использует заполнители и не позволяет произвольно переставлять аргументы, поэтому его поведение проще читать и проверять.
В C++11 для частичного применения функции часто использовали std::bind или лямбды. std::bind был мощным, но его заполнители, преобразования аргументов и особые правила передачи ссылок нередко делали код менее очевидным.
Лямбда обычно решала проблему понятнее, однако для простой фиксации начальных аргументов требовала ручного написания обёртки. std::bind_front появился в C++20 как более узкий и предсказуемый инструмент для такого сценария.
Допустим, функция принимает контекст или общий параметр первым, а callback должен получать только остальные аргументы. Неправильный выбор обёртки может привести к перестановке аргументов, неожиданным копиям или ошибкам времени жизни.
Особенно важно помнить, что bind_front обычно сохраняет связанные значения внутри созданного объекта. Передача изменяемого объекта без специального обёрточного типа не создаёт ссылочную связь с исходным объектом.
std::bind_front сохраняет вызываемый объект и связанные аргументы, а затем при вызове формирует вызов в порядке: сначала сохранённая функция, затем связанные аргументы, затем аргументы текущего вызова. Внутри используется универсальный механизм вызова std::invoke, поэтому поддерживаются обычные функции, функциональные объекты и указатели на методы.
В примере строка "network" сохраняется внутри обёртки, а число 503 передаётся при её вызове. Для обычных копируемых аргументов это означает хранение копии; если нужно передать ссылку, применяют std::ref или std::cref.
Связанные аргументы сохраняются с использованием семантики, аналогичной decay-копированию. Поэтому массив или функция обычно превращаются в указатель, а cv-квалификаторы и ссылки исходного выражения не сохраняются автоматически. Для некопируемых объектов обёртку создают с перемещением, после чего учитывают, можно ли вызывать её как const.
Аргументы, переданные при вызове обёртки, передаются дальше с сохранением их категорий значения. Это позволяет не терять перемещение временных объектов, если целевая функция его поддерживает.
Главное ограничение — bind_front фиксирует именно начальные аргументы. Он не поддерживает заполнители для пропуска или перестановки параметров. Если требуется сложная логика, условие, преобразование или нестандартный порядок аргументов, понятная лямбда обычно предпочтительнее.
В очереди задач есть функция отправки события, первым параметром которой является объект транспорта, а callback очереди должен принимать только полезную нагрузку. Рассматривались три варианта: ручная лямбда, std::bind и std::bind_front.
Лямбда наиболее гибка и явно показывает передачу параметров, но требует дополнительного кода. std::bind умеет переставлять параметры, однако заполнители усложняют чтение и диагностику ошибок. std::bind_front подходит, если транспорт действительно является первым параметром, и лучше выражает намерение.
Был выбран std::bind_front. Объект транспорта передали по ссылке через std::ref, поскольку очередь не должна владеть им, а его время жизни гарантируется внешним владельцем. В результате callback получился компактным, порядок аргументов остался очевидным, а лишнего копирования не произошло.
Лямбда позволяет явно описать алгоритм вызова, преобразовать аргументы, изменить порядок их передачи и выполнить дополнительные действия. bind_front предназначен только для частичного применения: он связывает начальные аргументы и делегирует вызов целевому объекту.
Обе конструкции могут хранить копии захваченных или связанных объектов, но у лямбды правила захвата видны непосредственно в исходном коде. Поэтому при сложной логике лямбда обычно лучше, а для простой фиксации первых аргументов bind_front компактнее.
В обёртке обычно сохранится копия объекта, а не ссылка на исходный lvalue. Последующие изменения исходного объекта не будут видны через bind_front, зато вызов не зависит от времени жизни исходного объекта.
Чтобы явно сохранить ссылочную семантику, используют std::ref для изменяемого объекта или std::cref для константного. При этом ответственность за то, чтобы исходный объект жил дольше обёртки, переходит к программисту.
Да, если связанный объект поддерживает перемещение и обёртка вызывается в подходящей категории значения. Однако повторный вызов после перемещения может быть некорректным с точки зрения состояния самого объекта.
Нужно также учитывать квалификаторы оператора вызова: некопируемый связанный объект может сделать вызов константной обёртки недоступным. Если требуется строго одноразовая операция, лямбда с явным перемещением или отдельный одноразовый объект часто лучше выражает это ограничение.