Сравнительно недавно в 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

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