Сравнительно недавно в stdlib плюсов появилось форматирование строк «как в питоне», а точнее, как в библиотеке fmt. И я, как смелый и отчаянный, решил этим воспользоваться. Возможно, аксакалы и настоящие разработчики скажут, что я всё делаю не так, и вообще не то, но я буду рад такой критике, если она поможет легче жить ;)

Итак, приступим. Вам понадобится свежий компилятор и стдлиб. На cppreference зеленеют красивые надписи (since C++20), круто! 2020-й уже давно прошёл, да и в мейкфайлах/vcxproj мы везде давно ставим --std=c++20, кто посмелее — даже больше. Статьи про std::format выходят уже несколько лет, и даже переводы на хабре есть [1], [2]. Значит, сейчас зафигачим маленький инклюдик, пройдёмся sed -e '...' по исходникам, и всё будет в шоколаде.

Не так быстро! Если, внезапно, микрософт впереди планеты всей, и format доступен как в свежей студии (2022), так в и в уже не свежей (2019), то если мы соберёмся собрать нашу программку, например, на убунту, тогда для разогрева нас сначала ждёт сайд-квест по установке самого нового gcc из ppa (или вообще из сорцов, если ваша система не самая модная и молодёжная, вы успеете сварить кофе, а если у вас нет кофеварки, то тоже успеете и заказать её, и получить, и сварить). Потому что фактически в GCC std::format появился совсем недавно. В шланге он есть якобы с 14й версии, но его даже не пробуйте, берите сразу 15+, потому что баги...

Ну что же, самым свежим компилятором и самым свежим stl мы обзавелись. (Кто-то скажет, надо было брать fmt и не выпендриваться, но мы же верим в светлое будущее!). Открываем, компилируем и запускаем Hello world! Работает! Но чувствуется привкус обмана... Во многих статьях упоминается, что это форматирование «почти как в питоне». Только данное «почти» слишком «почти». Возможно, такое сравнение прокатит для тех, кто никогда не видел питон, или какой-нибудь другой современный язык. Это совсем не f-strings из питона, и не interpolated string как в C#, и не template literals в JS/TS, и даже не костыль-макрос в Rust-е. C++ хоть и меняется семимильными шагами, но всё же, где ему угнаться до хипстерских языков. Ну штош, будем есть, что дают. К тому же, похожий формат применяется и в Zig, так что дальшейшие сравнения будут разве что с ним, а не с JS/C#/Rust (куда нам...).

Я что-то замечтался и отвлёкся, вернёмся к нашим баранам. У нас есть MSVC 14.36 и GCC 13.2, на них и будем пробовать. Встречайте, Hello world:

    std::cout << std::format("Hello {}!\n", "world");

Так работает, отлично! Нам обещали какие-то проверки валидности, давайте попробуем:

    std::cout << std::format("Hello {}!\n");
↓↓↓
/usr/local/gcc-13.2/include/c++/13.2.0/format:230:56: error: call to non-‘constexpr’ function ‘void std::__format::__invalid_arg_id_in_format_string()’
  230 |             __format::__invalid_arg_id_in_format_string();
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/usr/local/gcc-13.2/include/c++/13.2.0/format:180:3: note: ‘void std::__format::__invalid_arg_id_in_format_string()’ declared here
  180 |   __invalid_arg_id_in_format_string()
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

— Вжик, — сказала японская бензопила.
— Хм, — сказали суровые сибирские мужики.

    std::cout << std::format("Hello {!\n", "world");
↓↓↓
    /usr/local/gcc-13.2/include/c++/13.2.0/format:3468:68: error: call to non-‘constexpr’ function ‘void std::__format::__unmatched_left_brace_in_fomat_string()’
 3468 |                   __format::__unmatched_left_brace_in_format_string();
      |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/usr/local/gcc-13.2/include/c++/13.2.0/format:165:3: note: ‘void std::__format::__unmatched_left_brace_in_format_string()’ declared here
  165 |   __unmatched_left_brace_in_format_string()
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

— Вж-жик, — сказала японская бензопила.
— Хммм... — сказали суровые сибирские мужики.

    std::cout << std::format("Hello {}!\n", "cruel", "world");
↓↓↓
# ./a.out
Hello cruel!

— Дзинь, — сказала японская бензопила.
— Агааа! — сказали суровые сибирские мужики.

Да, какие-то проверки есть, и даже форматную строку оно проверяет! Но такой простой проверки, как количество аргументов нет. (Голосом Михалкова) Вас ки-ну-ли. Придётся всё самим, всё самим. Вот, например, как с этим справляются в одном из логгеров: binlog/create_source_and_event.hpp.

Конечно, форматную строку придётся распарсить заново. А ещё вопрос, почему во времена c++20 мы всё ещё используем макросы? Ага, это отличный вопрос, сейчас не будем углубляться, но хорошо, что заметили ;) Очевидно, что count_placeholders по ссылке не выдерживает никакой критики, поэтому я напишу свой лунап^w count_placeholders:

constexpr std::size_t count_placeholders(const char* str) {
    std::size_t result = 0;
    for (std::size_t i = 0; str[i] != 0; ++i) {
        assert(str[i] != '%');
        if (str[i] == '{' && str[i + 1] != '{') {
            ++i;
            for (; str[i] != 0; ++i) {
                if (str[i] == '}' && str[i + 1] != '}') {
                    ++result;
                    break;
                }
            }
        }
    }
    return result;
}

Вот такого крокодила я написал для дополнительной проверки форматной строки. Эта конструкция не поддерживает пронумерованные аргументы вида {2} {3} {100500}, но в нашем случае (портировании printf) это и не требуется. Когда потребуется — можно будет расширить (например, выдавая в результат максимальный найденный индекс). На 4-й строке можно заметить ещё одну фишку — этот ассерт временный, он нужен на время миграции старого (супер-старого) кода на базе xxprintf, чтобы точно быть уверенным, что все старые форматы из кода выкосили и ничего не пропустили. В продакшене так делать, конечно, не стоит.

О боги ц++, спасибо, что есть constexpr и даже можно писать в нём сколько-то нетривиальные функции. Теперь обернём нашу проверку и std::format вместе:

template <class... Args>
void Log(int level, const char* fmt, Args&&... args) {
    static_assert(count_placeholders(fmt) == sizeof...(Args));
    std::cout << std::format(fmt, args...);
}

Хорошо я придумал, а, а?

error C2131: expression did not evaluate to a constant
ну или
error: non-constant condition for static assertion

Низзя! Нельзя так делать: constexpr может потреблять строки (в виде const char* / std::string / std::string_view), но передавать ни в какие вызовы не может. В стандартах что-то обсуждается на этот счёт, и даже есть какие-то костыльные библиотеки. Но самый простой способ — зафигачить старый добрый макрос! Вот, — скажете вы, — причина присутствия макросов в логгере. Но, я вас огорчу, это не единственная причина их использования. Если в данном примере закомментить static_assert, компилятор нас всё равно обломает :(

Хорошо, хорошо, я понял, макросы так макросы. Не жили хорошо, нечего и начинать:

#define Log(level, fmt, ...)                                                                                \
do {                                                                                                        \
    static_assert(count_placeholders(fmt) == std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value,\
        "Number of {} placeholders in format string must match number of arugments");                       \
    /* тут всякая логика логирования, проверка уровня, буферов, формат заголовка итд */                     \
    std::cout << std::format(fmt, __VA_ARGS__) << std::endl;                                                \
    /* конечно, никто в продакшене в stdout не пишет, но мы же только учимся... */                          \
} while (false)

Считать количество аргументов в макросе не так прикольно, как в темплейте, но это самое не-вырвиглазное, что я смог найти. Ура, оно наконец-то компилируется! И даже логи выводит, и проверки работают. Можно приступать к конвертированию всех вызовов, а потом уже как-нибудь разберёмся с выводом в правильное время и правильное место. Борьбу с особо креативными форматами вида "value = %3.14lgbtq++" я оставлю за скобками, всё же это скорее проблема printf, от которой мы хотим поскорее избавиться. Тут меня ждало несколько приятных моментов: благодаря compile-time проверкам были пойманы строчки с кривыми форматами, и даже одно место редко случающегося segfault-а. Так что переходить на std::format однозначно полезно ????????????

Все места вызова портировали, отлично. Пора нарастить мясо на скелет логгера. Для начала надо выяснить, как форматировать в предоставленный буфер. К счастью, о нас подумали, и сделали сразу две функции format_to и format_to_n, последняя из которых тут же уютно обосновалась в нашем макросе:

#define Log(level, fmt, ...)                                                                    \
    /* ... */                                                                                   \
    auto _l_count = std::format_to_n(_l_buff, _l_bufSize, fmt, __VA_ARGS__).size;               \
    /* ... */                                                                                   \

Обратите внимание на префикс локальных переменных, он нужен чтобы избежать конфликта с вызывающим кодом. Функция format_to_n ещё очень удобна тем, что в её возвращаемом значении есть желаемый размер буфера, на случай если с первого раза не хватило. Вот теперь всё хорошо и статья на этом могла бы закончиться (но вы же видите, где скролл?), если бы я не посмотрел на размер получившегося бинарника: O_o упс! Файл из 1.2 МБайта распух до 6! (шести, Карл!) Хотя в наше время чат-клиентов, занимающих сотни мегабайт на диске (и не меньше — в ram), негоже стесняться каких-то 6МБ, я решил, что так не пойдёт. Мы должны бороться со злом, а не примкнуть к нему! К тому же, это плохо скажется на icache.

К гадалке не ходи, ясно что код распух из-за инлайна самого макроса и мелких методов, в нём вызывающихся. В старом printf-based логгере я решил этот вопрос вынесением основного тела логгера в функцию, помеченную __declspec(noinline) (или __attribute__ ((noinline)), каждому компилятору своё). Давайте попробуем провернуть то же самое и с нашим новым кодом:

template <class... Args>
FIX_NOINLINE void LogWorker(int level, const char* fmt, Args&&... args) {
    // ...
    auto _l_count = std::format_to_n(_l_buff, _l_bufSize, fmt, args...).size;
    // ...
}
#define Log(level, fmt, ...)                                                                                \
do {                                                                                                        \
    static_assert(count_placeholders(fmt) == std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value,\
        "Number of {} placeholders in format string must match number of arugments");                       \
    LogWorker(level, fmt, __VA_ARGS__);                                                                    \
} while (false)
    
    ↓↓↓
    
1>Logger.hpp(193,71): error C7595: 'std::basic_format_string<char>::basic_format_string': call to immediate function is not a constant expression
1>C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.36.32532\include\format(3384,63): message : failure was caused by a read of a variable outside its lifetime
1>C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.36.32532\include\format(3384,63): message : see usage of 'fmt'
... ещё пара сотен строк шаблонных ругательств от компилятора ...

В смысле?! Ах да, это же та самая проблема, где нельзя передать дальше константную форматную строку, из-за которой мы и городили макрос. Но как же оно внутри работает и не держит при этом 4 копии одного и того же кода? Тут есть 2 пути: нажать F12 и провалиться в метод, или пойти читать документацию. И тут и там мы увидим методы vformat***, братьев-близнецов тех, которые мы уже используем. Плюсы: им можно передавать любой формат, минусы: они его не валидируют, им пофиг и нет функции vformat_to_n, хотя в оригинале её таки сделали. Где логика?™

Для статической проверки форматной строки требуется const std::format_string<Args...> fmt. И вот его нет в несвежей студии, потому что в стандарте он появился не сразу, так что снова придётся городить макрос:

#define fmt_get get()
#ifdef _MSC_VER
#  if _MSC_VER <= 1935
#    define format_string _Fmt_string
#    undef fmt_get
#    define fmt_get _Str
#  endif
#endif

Отлично, почти всё что требуется мы уже закостылили! Но продолжаем ныть дальше: если вы пользуетесь только MS Visual Studio, то готовьтесь к абсолютно невменяемым текстам ошибок. Начиная с call to immediate function is not a constant expression по любому поводу (и без), и заканчивая километровыми портянками, в которых STL срыгивает на вас своё нутро. Я уверен, в аду припасён отдельный котёл для разработчиков STL, в котором они будут читать сообщения об ошибках в темплейтах до скончания веков... И ещё один по соседству — для boost-а. В gcc ошибки почти такие же длинные (шаблоны же), но чуть более адекватные.

Bonus track

Естественно, после решения всех насущных проблем захотелось большего: в частности, форматирования кастомных типов. Статей на эту тему тоже полно, поэтому тут подвоха я не ожидал. Первый попавшийся пример — полноценный форматтер из двух методов: parse и
format. На каждый мелкий енум городить такое не хочется, поэтому скроллим дальше и находим пример с унаследованным форматтером:

template<> struct std::formatter<MyEnum> : formatter<const char*> {
    auto format(auto v, auto& ctx) { return formatter<const char*>::format(MyEnumToString(v), ctx); }
};

И он даже сразу заработал! Аллилуйя! ...Ха-ха, у gcc на этот счёт свои соображения:

/usr/local/gcc-13.2/include/c++/13.2.0/format:3270:38: error: no matching function for call to ‘std::basic_format_arg<std::basic_format_context<std::__format::_Sink_iter<char>, char> >::basic_format_arg(MyEnum&)’
 3270 |           basic_format_arg<_Context> __arg(__v);
      |                                      ^~~~~
... ещё 3 километра текста ошибок ...

(ノಠ益ಠ)ノ彡┻━┻ Что? Что ему опять надо??? К счастью, на этот раз ответ легко загуглить, gcc не хватает совсем чуть-чуть. Найдите 6 отличий:

template<> struct std::formatter<MyEnum> : formatter<const char*> {
    auto format(auto v, auto& ctx) const { return formatter<const char*>::format(MyEnumToString(v), ctx); }
};

И последнее, про генерируемый код. Я, конечно, ожидал что будет что-то похожее на то, как делает Zig, ну или хотя-бы частично так. Ведь в этом весь прикол заранее известной форматной строки. Вот что генерирует зиг для Hello world:

zig decompiled
zig decompiled

И это не просто весь код main, а почти весь код бинарника, который весит 5кб и не имеет импортов! Ладно, от C++ я такого не жду, но хотя-бы побить строку на части можно было?

gcc decompiled
gcc decompiled

А шиш там! Мы ещё и форматную строку будем каждый раз в рантайме парсить. Причём gcc (на скрине выше) генерирует самый приличный код из «большой троицы» (не считая сотен функций в бинарнике и ещё десятки импортированных).

Выводы

Несмотря на все эти неприятности, однозначно стоит переходить из каменного века (printf) и тёмных веков средневековья (iostreams). Даже в таком не самом оптимальном виде std::format ни капли не медленнее printf и сильно быстрее iostream. Плюс мы получаем проверки формата во время компиляции, и даже можем дополнительно написать свои.

И в случае если вы не можете просто так обновить свой компилятор, советую попробовать библиотеку fmtlib, из которой собственно std::format и вырос. Она работает начиная с C++11 и используется во многих крупных проектах. Так что рекомендую!

А где-нибудь к C++30 мы получим форматирование как в питоне и других современных языках, я надеюсь ;)

Комментарии (26)


  1. Kelbon
    27.09.2023 09:45
    +2

    Да, какие-то проверки есть, и даже форматную строку оно проверяет! Но такой простой проверки, как количество аргументов нет.

    auto foo(auto&&... args) {
      return std::format("{1} {6}", args...);
    }

    Если вам хочется дополнительно проверить, что количество аргументов совпадает с количеством {}, то сделайте

    template<typename... Types>
    struct my_fmt_string : std::format_string<Types...> {
      consteval my_fmt_string() { // additional checks  }
    };
    std::string myformat(my_fmt_string) {
    ...
    }
    




    1. atd
      27.09.2023 09:45

      Спасибо, дельный совет! К сожалению, наличие std::format_stringя увидел только после обновления студии до самой свежей. А до этого у меня УХ как подгорало...

      Таким трюком можно полностью избавиться от макросов (почти, если не считать совместимость с несвежими VS и clang-ом)


  1. Kelbon
    27.09.2023 09:45
    +2

    . Причём gcc (на скрине выше) генерирует самый приличный код из «большой троицы» (не считая сотен функций в бинарнике и ещё десятки импортированных).

    кажется это типичная ошибка при работе с гцц - писать бенчмарк в мейне. Гцц по одной им известной причине по другому оптимизирует мейн нежели остальные функции

    Нет, не хорошо, создаётся лишняя строка которая только потом выводится в cout, вместо этого лучше использовать format_to + ostreambuf_iterator

    https://godbolt.org/z/xen6cT4h1

    Мы ещё и форматную строку будем каждый раз в рантайме парсить.

    А вот это проблема, но решаемая, нужно чтобы реализации использовали факт проверенности строки на компиляции(кажется пока это закрывается спецификацией которая чётко говорит, что format должен вызывать vformat)


    1. atd
      27.09.2023 09:45
      +3

      Спасибо, про main познавательно. Про последний пункт — да, надеемся.

      Нет, не хорошо

      У вас ссылка непосещённая ;)


  1. rsashka
    27.09.2023 09:45
    -1

    Плюс мы получаем проверки формата во время компиляции, и даже можем дополнительно написать свои.

    Если это уже есть и в printf, то зачем городить огород? Чтобы строка формата как в Python, автоматический вывод типа или еще что-то?


    1. Kelbon
      27.09.2023 09:45
      +1

      1. этого нет в printf

      2. printf это сишный вариадик который вносит рантайм оверхед(помимо прочих проблем)

      3. не кастомизируем под свои типы(даже с расширениями), не переносим(нужно писать макросы в формат строке) и тд


      1. rsashka
        27.09.2023 09:45

        этого нет в printf

        Нет чего, проверки форматов во время компиляции? А как же __attribute__ ((format(printf, 1, 2))); Если этого нет в компиляторе MS, то этого нет именно там, но не у printf, для которой проверка формата есть.

        printf это сишный вариадик который вносит рантайм оверхед(помимо прочих проблем)

        Который проверен десятилетиями и изучены всего подводные камни? И чем это плохо? А насчет оверхеда, я специально для embedded использую только printf, потому что с оверхедом при парсинге строки формата справляется самый простой микроконтроллер, а вот лишние 200-300 кБайт ПЗУ только для того, чтобы подключить форматированные ввод/вывод потоков это очень серьезный оверхед, который съедает в первую очередь деньги, т.к. заставляет использовать более старшие (и более дорогие) версии кристаллов.

        не кастомизируем под свои типы(даже с расширениями), не переносим(нужно писать макросы в формат строке) и тд

        Ни разу не возникало необходимости кастомизировать свои типы данных для форматного вывода, т.к. эта задача решается другим способом (конвертируешь свой тип данных в строку и выводишь как строку). И для этого не требуется новый форматный вывод.
        А чтобы не писать макросы, можно определить собственную функцию с форматироваными аргументами как у printf, так же можно сделать и в предыдущем случае.

        Я не стараюсь вас убедить использовать старый и проверенный годами printf. Хотите использовать новомодный std::format_string, пожалуйста, это ваше право. Просто для себя я не увидел в нем ни одного ключевого преимущества, который бы заставил на него перейти.

        Поэтому мой предыдущий комментарий про "зачем городить огород, есть printf" для меня актуален, так как я действительно хочу разобраться в вопросе. Но пока аргументы против printf притянуты за уши, но может быть я чего-то не знаю или не обращаю внимания?


        1. Kelbon
          27.09.2023 09:45

          А чтобы не писать макросы, можно определить собственную функцию с форматироваными аргументами как у printf, так же можно сделать и в предыдущем случае.

          не поможет, если нужно вывести инт, а он на разных плафтормах разный, то ни шаблоны ни другие функции не помогут, придётся использовать макросы и пихать в формат строку

          конвертируешь свой тип данных в строку и выводишь как строку

          неэффективно

          ввод/вывод потоков это очень серьезный оверхед

          потоки это другое, не связано с std::format. Они действительно добавляют в бинарь своего, да и в целом неэффективны. Но формат к ним никак не относится, std::print, который внутри использует std::format, можно использовать вместе с FILE* и каким нибудь stdout


          1. rsashka
            27.09.2023 09:45
            -2

            не поможет, если нужно вывести инт, а он на разных плафтормах разный, то ни шаблоны ни другие функции не помогут, придётся использовать макросы и пихать в формат строку

            Сделай static_cast<int> для аргумента и использую единую форматную строку для всех платформ. Я реально не понимаю, зачем тут макросы?

            неэффективно

            Конечно, гораздо эффективнее подключить отдельную библиотеку, хотя свой формат в строку все равно придется конвертировать перед выводом.

            потоки это другое, не связано с std::format....

            Но это не ответ на вопрос, зачем прикручивать лишнюю библиотеку и менять работающее годами решение на другое, если у нового него нет серьезных преимуществ?


            1. Kelbon
              27.09.2023 09:45

              Сделай static_cast<int>

              дополнительное поле для ошибок, оверхеда, кривого кода. Интересно как выводить unsigned если кастовать всё к инту... А как выводить __int128? Ну и конечно любые свои типы, какая-нибудь точка своя в геометрии. Ну или ренж таких точек. В общем придётся на каждый случай писать кучу бойлерплейта с возможностью ошибиться оочень много раз

              хотя свой формат в строку все равно придется конвертировать перед выводом.

              не придётся, вместо того чтобы создавать строку и выводить просто выводишь поля друг за другом, например

              если у нового него нет серьезных преимуществ?

              они есть, если вам не очевидны минусы printf, то тут ничем не могу помочь


              1. rsashka
                27.09.2023 09:45

                Я ответил ответил на ваше возражение

                не поможет, если нужно вывести инт, а он на разных плафтормах разный, то ни шаблоны ни другие функции не помогут, придётся использовать макросы и пихать в формат строку

                простейшим примером, как это можно сделать без макросов и разных форматных строк. Поэтому ненужно накручивать то, чего не было в изначально озвученной проблеме.

                просто выводишь поля друг за другом, например

                С такой же легкостью можно и в printf, поэтому опять возражение мимо.

                они есть, если вам не очевидны минусы printf, то тут ничем не могу помочь

                Это не вопрос веры или суслика (ты его не видишь, а он есть), а реальных фактов, если вы конечно в теме, но вы так и не смогли привести ни одного (в отличии от @atd).


                1. Kelbon
                  27.09.2023 09:45

                  простейшим примером, как это можно сделать без макросов и разных форматных строк. Поэтому ненужно накручивать то, чего не было в изначально озвученной проблеме.

                  придётся выдумывать каждый раз как это сделать

                  С такой же легкостью можно и в printf, поэтому опять возражение мимо.

                  нет,
                  1. придётся много раз вызывать printf

                  2. каждый раз придётся повторять код, нельзя вынести это в форматтер

                  если вы конечно в теме, но вы так и не смогли привести ни одного 

                  я привёл, вы просто не считаете это проблемами по непонятной причине

                  вот, перепишите на printf

                  void log(auto&& x) {
                    std::print("{}", x);
                  }


                  1. rsashka
                    27.09.2023 09:45
                    -2

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

                    Сделать свой форматтер, если не хочется повторяться.

                        va_list args;
                        va_start(args, format);
                        vsnprintf(buffer, LOG_MAX_BUFFER_SIZE, format, args);
                        va_end(args);
                    

                    Я вижу более простые решения для приведенных вами примеров, поэтому и не считаю их серьезными аргументами для замена проверенного и работающего решения на что-то новое.

                    вот, перепишите на printf

                    Не буду. Но не потому, что не хочу или не могу, а не вижу смысла в такой конструкции логирования. Я считаю, что разработчик должен понимать, что он выводит в лог и в каком виде (чтобы это не было 1Гб двоичных данных). И с этой точки зрения printf с проверкой типов аргументов для форматной строки является почти идеальным решением.


                    1. Kelbon
                      27.09.2023 09:45

                      Не буду. Но не потому, что не хочу или не могу,

                      могли бы сразу сказать, что вам единственное что нужно это хоть как нибудь вывести через 3 костыля int(и не дай бог это int32_t), а остальное не нужно, не имеет смысла и так далее


                      1. rsashka
                        27.09.2023 09:45
                        -1

                        Ни в коем случае.
                        Наоборот, я очень активно пользуюсь отладочным выводом, причем реализовано это кроссплатформенно и единообразно сразу для всех платформ (Linux, Windows, FreeRTOS на x86, x64, Cortex, GigaDevice) без изменений исходников и возможностью переназвачения вывода на экран, в файл, UART или даже во Flash память микроконтроллера для посмертного CoreDump


        1. atd
          27.09.2023 09:45

          А насчет оверхеда, я специально для embedded использую только printf

          Ого, как времена изменились! Раньше сам printf в ембеде считался преступлением, и использовались отдельные форматтеры (иногда даже самописные), чтобы сэкономить 20кб принтфа (ещё и стэка неизвестно сколько он сожрёт). А теперь это «без оверхеда» уже считается? Oo

          Но с тем, что в ембеде std::format пока не нужен — соглашусь. Хотя и принтф тоже там не нужен )


          1. rsashka
            27.09.2023 09:45

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

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


            1. atd
              27.09.2023 09:45
              +1

              Я не знаю, когда и у кого он считался преступлением

              Во времена pic16f84a.

              Ваша основная (и справедливая) претензия — к iostream, но его использовать совсем не обязательно, вы можете сделать (как показано в статье):

              char buff[BUFSIZE];
              std::format_to_n(buff, BUFSIZE...)

              Или вообще взять `format_to` и писать сразу в UARTn->DR.

              можно отключать не нужные форматы вывода (например числа с плавающей запятой).

              Весь прикол std::format в том, что не надо ничего отключать, ненужны форматы сами не вкомпилятся в результирующий бинарник.


              1. rsashka
                27.09.2023 09:45
                +1

                Спасибо, это действительно реальный аргумент за использование std::format


  1. kovserg
    27.09.2023 09:45

    Я вот лично не очень понимаю все эти лишние проверки


        std::cout << std::format( load_string(ID_HELLO,lang), arg1, arg2 );

    И что в таком случае надо проверять?


    И как вообще выравнивать строки влево в право, если ширина в юникоде/utf8 очень не очевидная величина.


    А что насчет шаблонов текста то же ничего ни фильтров, ни именованных полей?


    В общем очень прорывная технология, аж уши закладывает. /s


    1. Kelbon
      27.09.2023 09:45

      И что в таком случае надо проверять?

      что строка известна на этапе компиляции для начала


    1. atd
      27.09.2023 09:45

      А что насчет шаблонов текста

      а про это целый параграф нытья в статье есть

      В общем очень прорывная технология

      это С++, детка, так и живём...


  1. crackedmind
    27.09.2023 09:45
    +1

    Да, какие-то проверки есть, и даже форматную строку оно проверяет! Но такой простой проверки, как количество аргументов нет.

    Это сделано сознательно, так как можно написать std::format("{1} {1} {0} {0}", 9, 6)


    1. atd
      27.09.2023 09:45
      +2

      Так можно, там ещё много что можно. Но вот этот случай тоже вполне проверяемый.


      1. domix32
        27.09.2023 09:45

        А constexpr kv-контейнеры уже завезли под валидацию такого?


        1. atd
          27.09.2023 09:45

          нет, но они и не требуются