Как в C++20 отформатировать строку, полученную во время выполнения, не передавая её в интерфейс, рассчитанный на формат, проверяемый при компиляции?
Для динамической форматной строки используют std::vformat вместе с std::make_format_args. В отличие от вызова std::format с литералом, формат проверяется во время выполнения; при некорректной строке возникает std::format_error.
Традиционные средства вроде printf принимают форматную строку как обычный аргумент и не связывают её спецификаторы с типами остальных аргументов на уровне интерфейса. Ошибка в спецификаторе могла привести к неопределённому поведению, повреждению данных или трудно диагностируемому выводу.
std::format из C++20 проектировался как типобезопасная альтернатива с единым синтаксисом форматирования. Для строковых литералов библиотека может проверить соответствие формата аргументам до запуска программы.
В библиотечном или прикладном API форматная строка часто поступает из конфигурации, локализации, шаблона сообщения или пользовательского ввода. Такая строка неизвестна во время компиляции, поэтому её нельзя передать в интерфейс, который требует compile-time формат.
Попытка использовать динамическую строку как обычный первый аргумент std::format приводит к ошибке компиляции в реализациях, где этот интерфейс принимает std::format_string. Замена на std::vformat решает проблему, но переносит проверку корректности формата на время выполнения.
std::vformat принимает формат как обычный std::string_view и набор аргументов типа std::format_args. Этот набор создаётся функцией std::make_format_args из фактических аргументов.
std::make_format_args формирует представление аргументов, а не обязательно независимые копии их значений. Поэтому созданный набор аргументов нельзя безопасно сохранять для последующего использования без отдельного управления временем жизни исходных объектов; типичный сценарий — передать его непосредственно в std::vformat.
Если форматная строка некорректна или спецификатор не соответствует переданному аргументу, во время выполнения выбрасывается std::format_error. Код, принимающий внешние шаблоны, должен учитывать это исключение и при необходимости заранее ограничивать допустимый синтаксис.
Для известного на этапе компиляции литерала предпочтителен обычный std::format: он сохраняет compile-time проверку формата и обычно позволяет обнаружить ошибку раньше. Для динамической строки такой гарантии нет, поэтому выбор между двумя интерфейсами определяется временем известности формата.
Важно не путать динамический формат с динамическим значением аргумента. Значение, например имя пользователя, может быть динамическим и при этом безопасно передаваться в обычный std::format; динамической является именно строка-шаблон.
Сервис локализации хранит шаблоны сообщений в файлах: шаблон загружается при запуске и содержит заменители для имени и числа операций. Использовать std::format с таким шаблоном напрямую нельзя, поскольку компилятор его не видит.
Вариант с ручной конкатенацией строк не требует проверки формата, но усложняет локализацию, выравнивание, числовые спецификаторы и обработку разных типов. Вариант с std::format подходит только после превращения шаблонов в compile-time данные, что противоречит требованию загружать их без перекомпиляции.
Выбранное решение — std::vformat с std::make_format_args внутри функции формирования сообщения. Ошибка шаблона перехватывается на границе загрузки или при первом использовании, после чего сервис помечает локализацию некорректной, а не допускает аварийное завершение в произвольной точке обработки запроса.
Можно ли передать динамическое значение в обычный std::format, если формат является литералом?
Можно. Compile-time проверяется форматная строка и её спецификаторы, а значения аргументов могут быть получены во время выполнения. Динамичность аргумента не требует перехода к std::vformat; переход нужен именно для динамической форматной строки.
Почему нельзя бездумно сохранить результат std::make_format_args?
std::format_args — это type-erased представление аргументов, рассчитанное на их использование в течение соответствующего вызова. Оно может ссылаться на исходные объекты и временные значения, поэтому сохранение набора дольше времени жизни этих объектов создаёт риск обращения к недействительным данным. Если форматирование нужно отложить, следует сохранять собственные значения и создавать новый набор аргументов непосредственно перед форматированием.
Делает ли std::vformat внешнюю форматную строку безопасной от любых проблем?
Нет. Он исключает ошибки несоответствия типов в стиле printf, но некорректный или чрезмерно сложный шаблон всё равно может вызвать std::format_error, большое потребление памяти или затраты времени. Формат из недоверенного источника нужно валидировать, ограничивать по размеру и обрабатывать ошибки; std::vformat не является механизмом авторизации или фильтрации пользовательского содержимого.