std::format в C++20: современное форматирование строк с примерами и лучшими практиками

std::format в C++20: современное форматирование строк с примерами и лучшими практиками

std::format в C++20: современное форматирование строк с примерами и лучшими практиками

Если вам надоело возиться с iostream‑манипуляторами и опасными printf, самое время освоить std::format в C++20. Он даёт привычный форматный синтаксис, типобезопасность и производительность. Разберёмся на практических примерах и типичных ошибках, чтобы вы смогли сразу применять std::format в рабочих проектах.

Быстрый старт: как подключить и скомпилировать

Поддержка std::format есть в современных компиляторах (GCC 13+, Clang 16+, MSVC 19.3x+). Минимальный пример:

#include <format>
#include <iostream>

int main() {
    std::string name = "Алиса";
    auto s = std::format("Привет, {}!", name);
    std::cout << s << 'n';
}

Компиляция (GCC/Clang):
g++ -std=c++20 main.cpp -O2 -o app

В MSVC достаточно включить стандарт C++20 в настройках проекта.

Синтаксис std::format: место для каждого аргумента

  • Плейсхолдер {} — вставка значения по порядку аргументов.
  • После двоеточия задаётся спецификатор формата: {<spec>}.
  • Внутри можно указывать выравнивание, ширину, заполнитель, систему счисления, точность и знак.
  • #include <format>
    #include <iostream>
    
    int main() {
        // Выравнивание и ширина
        std::cout << std::format("[{:<8}] [:{:^8}] [:{:>8}]n", 42, 42, 42);
        // Заполнитель (fill)
        std::cout << std::format("{:*^10}n", "C++"); // ***C++***
    
        // Числа: ведущие нули, знак, системы счисления
        std::cout << std::format("{:08d}n", 123);   // 00000123
        std::cout << std::format("{:+d}n", 5);      // +5
        std::cout << std::format("{:#x}n", 255);    // 0xff
        std::cout << std::format("{:#b}n", 10);     // 0b1010
    
        // Вещественные числа и точность
        std::cout << std::format("{:.2f}n", 3.14159);  // 3.14
        std::cout << std::format("{:.3e}n", 12345.0); // 1.235e+04
    }
    

    std::format vs printf и iostream

  • Типобезопасность: несовпадение типов ловится на этапе компиляции, если формат — строковый литерал (используется форматный тип format_string).
  • Удобство: один стиль форматирования вместо десятков манипуляторов iostream.
  • Производительность: сравнима с printf, при этом без опасных UB из‑за неверных спецификаторов.
  • Форматирование времени (chrono)

    В C++20 можно красиво форматировать время. Чаще всего достаточно округлить до секунд и задать шаблон:

    #include <format>
    #include <chrono>
    #include <iostream>
    
    int main() {
        using namespace std::chrono;
        auto now = system_clock::now();
        auto s = std::format("{:%Y-%m-%d %H:%M:%S}", floor<seconds>(now));
        std::cout << s << 'n';
    }
    

    Шаблоны формата схожи с strftime: %Y — год, %m — месяц, %d — день, %H:%M:%S — время.

    Печать без cout: std::print из C++23

    Если у вас C++23, можно печатать напрямую:

    #include <print>
    
    int main() {
        std::print("Hello, {}!n", "world");
    }
    

    В C++20 это ещё недоступно — пользуйтесь std::format + std::cout.

    Собственный тип: пишем форматтер за 2 минуты

    std::format умеет форматировать ваши структуры, если вы напишете специализацию std::formatter. Пример для точки:

    #include <format>
    #include <iostream>
    
    struct Point { int x; int y; };
    
    template<>
    struct std::formatter<Point> {
        // Простой форматтер: игнорируем формат-спеки
        constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
    
        template<class FormatContext>
        auto format(const Point& p, FormatContext& ctx) const {
            return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
        }
    };
    
    int main() {
        Point p{3, 4};
        std::cout << std::format("P = {}n", p);
    }
    

    Так вы можете описывать читаемый вывод для доменных сущностей и логов, не засоряя код ручной сборкой строк.

    Типичные ошибки и как их избежать

  • Несовпадение типов и спецификаторов. Со строковым литералом компилятор укажет на ошибку; с динамической строкой будет выброшено исключение std::format_error в рантайме.
  • Забыли подключить заголовок: нужен <format> (и иногда <chrono> для времени).
  • Старая версия компилятора. Обновитесь до GCC 13+/Clang 16+/актуального MSVC или временно используйте {fmt}.
  • Если компилятор ещё не поддерживает std::format

    Можно подключить библиотеку {fmt}, на которой основан стандартный std::format. API практически совпадает, а переход позже займёт минуты:

    #include <fmt/core.h>
    #include <fmt/chrono.h>
    
    int main() {
        fmt::print("Привет, {}!n", "мир");
    }
    

    Небольшая памятка по спецификаторам

  • Выравнивание: ":<width" (влево), ":^width" (по центру), ":>width" (вправо). Заполнитель указывается перед выравниванием: ":*^10".
  • Числа: d (10‑я), x/X (16‑я), b (2‑я), префикс # добавляет 0x, 0b. Ведущие нули: 08d. Знак: + для положительных тоже.
  • Вещественные: f (фикс), e (экспонента), g (умно выбирает), точность: .N.
  • Строки и char форматируются без танцев с бубном: просто {} или с выравниванием.
  • Практические советы

  • Выносите форматные строки в константы — проще переиспользовать и тестировать.
  • Для логирования определяйте форматтеры доменных типов — логи станут короче и понятнее.
  • Не смешивайте старые printf с std::format: держите проект в едином стиле форматирования.
  • Проверяйте поддержку на CI разными компиляторами, чтобы не поймать сюрпризы.
  • Что дальше изучать

    Освоив std::format в C++20, вы заметите, как ускоряется разработка: меньше шума в коде и меньше ошибок типов. Если хотите системно подтянуть C++ (включая современные фичи стандарта и практику), рекомендую пройти качественный курс: Прокачать C++ с нуля до уровня Гуру на практическом курсе.

    Итоги

    std::format в C++20 — удобный, безопасный и мощный инструмент для форматирования строк. Он заменяет связку iostream+манипуляторы и устаревший printf, даёт понятный синтаксис, расширяется под ваши типы и открывает путь к ещё более лаконичному std::print в C++23. Применяйте его в логировании, отчётах, диагностике и пользовательских сообщениях — код станет чище и надёжнее уже сегодня.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий