LINUX.ORG.RU

fmt 10.0.0

 , ,


1

4

После восьми месяцев разработки состоялся выпуск 10.0.0 библиотеки форматирования данных fmt — быстрой и безопасной альтернативы stdio и iostreams для C++.

#include <fmt/color.h>

int main() {
    fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
           "Elapsed time: {0:.2f} seconds", 1.23);
}

Список изменений:

  • для форматирования чисел с плавающей точкой с заданной точностью взамен алгоритма Grisu используется алгоритм Dragonbox, что дало существенное улучшение производительности;
  • для форматирования чисел с плавающей точкой в шестнадцатеричном представлении вместо ранее использованного snprintf применяется собственная реализация;
  • улучшена поддержка модулей C++20;
  • format_as теперь поддерживает любой пользовательский тип, а не только перечисления:
#include <fmt/format.h>

struct floaty_mc_floatface {
  double value;
};

auto format_as(floaty_mc_floatface f) { return f.value; }

int main() {
  fmt::print("{:8}\n", floaty_mc_floatface{0.42}); // prints "    0.42"
}
  • для совместимости с std::format удалены устаревшие неявные преобразования для перечислений и преобразования к примитивным типам;
  • улучшена поддержка std::chrono:
#include <fmt/chrono.h>

int main() {
  // prints "    2023"
  fmt::print("{:>8%Y}\n", std::chrono::system_clock::now());
}

#include <fmt/chrono.h>

int main() {
  // prints 01.234567
  fmt::print("{:%S}\n", std::chrono::microseconds(1234567));
}
  • добавлена поддержка std::utc_time;
  • добавлена поддержка std::exception:
#include <fmt/std.h>
#include <vector>

int main() {
  // prints: "vector<bool>::_M_range_check: __n (which is 0) >= this->size() (which is 0)"
  try {
    std::vector<bool>().at(0);
  } catch(const std::exception& e) {
    fmt::print("{}", e);
  }
}
  • улучшена поддержка стандартных контейнеров:
#include <fmt/ranges.h>
#include <stack>
#include <vector>

int main() {
  auto s = std::stack<bool, std::vector<bool>>();
  for (auto b: {true, false, true}) s.push(b);
  fmt::print("{}\n", s); // prints [true, false, true]
}
  • добавлена поддержка std::optional;
  • в fmt::ptr добавлена поддержка unique_ptr с пользовательским деструктором;
  • улучшена обработка некорректного Юникода в путях файлов;
  • добавлена экспериментальная поддержка Юникодных разделителей цифр :
c++
auto loc = std::locale(
  std::locale(), new fmt::format_facet<std::locale>("’"));
auto s = fmt::format(loc, "{:L}", 1000);
  • для совместимости с basic_format_string добавлена fmt::basic_format_string::get();
  • для совместимости с C++23 добавлена функция println;
  • улучшения документации и тестов.

Множество других исправлений и улучшений.

>>> Подробности

★★★★

Проверено: hobbit ()
Последнее исправление: hobbit (всего исправлений: 1)

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

Правильно:

var errorMsg = "";
print($"Error: {errorMsg}");

Говно:

auto errorMsg = "";
print("Error: {}", errorMsg);
ox55ff ★★★★★
()
Ответ на: комментарий от skvitek

На что тебе пруф? На то что в строке print("{}{}{}{}{}{}", a, b, c, d, e, f) сложно понять что куда подставляется, а значит легче совершить ошибку? Это очевидно.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

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

простое форматирование там можно было бы сделать препроцессором по идее…

anonymous2 ★★★★★
()
Последнее исправление: anonymous2 (всего исправлений: 1)
Ответ на: комментарий от Anoxemian

строгой формальной логики

Миллиард уязвимостей в printf когда путают тип переданной переменной и плейсхолдер (%s, %d и т.д.) теперь называется формальной логикой?

ox55ff ★★★★★
()
Ответ на: комментарий от Siborgium

Потому что fmt жертва инерции мышления. Нужно заниматься не автоматическим определением типа переданных данных, а вообще выкинуть концепцию отделения строки с плейсхолдерами и списка переменных.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

К интерполяции переменных слишком много вопросов, чтобы поддерживать ее в С++.

Например,

std::string world;
const char* lit = $"Hello, {world}";

валидно? Где взять память под литерал, куда копировать содержание world?

Какой вообще тип должно возвращать выражение $«Hello, {world}»? Я могу вернуть его из функции? Лениво подставлять значение переменных, или нет? Захватывать по ссылке или значению? Что делать с лайфтаймами? Как добавить форматирование для кастомного типа? Что делать с локалями: пусть я в $"{a}{b}" хочу a форматировать в одной локали, а b в другой. В целом как передавать параметры? Как мне в foo(const char* c) передать результат $"{x}", если там не const char*?

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

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от ox55ff

На что тебе пруф? На то что в строке print(«{}{}{}{}{}{}», a, b, c, d, e, f) сложно понять что куда подставляется, а значит легче совершить ошибку? Это очевидно.

Так это только один из вариантов синтаксиса fmt() - когда параметры нумеруются автоматически. Используй явные номера параметров, если тебе так понятнее -
fmt(«{1}{0}{2}»,«два»,«один»,«три»);

sigurd ★★★★★
()
Ответ на: комментарий от ox55ff
print($"Error: {errorMsg}");

А как понять, что это переменная, а не вывод errorMsg в фигурных скобках?

IDE поможет разобраться и покажет, где объявлена переменная?

dataman ★★★★
() автор топика
Ответ на: комментарий от dataman

Вывод errorMsg в фигурных скобках это $"{{errorMsg}}" или $"\{errorMsg\}" – это вопрос синтаксиса, не более.

IDE поможет разобраться и покажет, где объявлена переменная?

Да, в языках с поддержкой строковой интерполяции IDE обычно понимают это. Это тоже не проблема.

Реальные проблемы с поддержкой строковой интерполяции в С++ я обрисовал выше.

Siborgium ★★★★★
()
Ответ на: комментарий от ox55ff

Потому что fmt жертва инерции мышления. Нужно заниматься не автоматическим определением типа переданных данных, а вообще выкинуть концепцию отделения строки с плейсхолдерами и списка переменных.

Так скинь ссылку на свою реализацию форматирования!
А то критиковать каждый может!

sigurd ★★★★★
()
Ответ на: комментарий от Siborgium

Вывод errorMsg в фигурных скобках это $"{{errorMsg}}"

Это правильный вариант.

А в «правильном» - непонятно. :)

dataman ★★★★
() автор топика
Ответ на: комментарий от ox55ff

Сравнивать строку и вывод переменных - не позорился бы.

grem ★★★★★
()
Ответ на: комментарий от ox55ff

На что тебе пруф? На то что в строке print(«{}{}{}{}{}{}», a, b, c, d, e, f) сложно понять что куда подставляется, а значит легче совершить ошибку?

Кому сложно? Мне не сложно.

Это очевидно.

Нет

skvitek ★★★
()
Ответ на: комментарий от ox55ff

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

Кому нужно? Мне не нужно.

когда путают тип переданной переменной и плейсхолдер (%s, %d и т.д.)

А ты не путай.

skvitek ★★★
()
Ответ на: комментарий от Siborgium

Это должен быть сахар, который конвертируется в вызов библиотечной функции. Если этот ваш fmt и любая другая форматиловка может отвечать на поставленные тобой вопросы, то и сахар сможет. Не вижу проблем.

const char*

Это не c++. У любого способа форматирования в рантайме будут проблемы в этом случае.

ещё 100500 сложных кейсов применения

Это неправильно. Нужно двигаться в сторону удобства, а не выдумывания комбайна, который может проглотить всё. Достаточно, чтобы сахар с интерполяцией поддерживал 90% распространённых кейсов форматирования. Это значительно упростит код. А для 10% сложных случаев можно вручную отформатировать.

ox55ff ★★★★★
()
Ответ на: комментарий от dataman

$ перед " включает интерполяцию. Если нужны фигурные скобки в режиме интерполяции, то используется экранирование.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

А потом 100500 багов с ошибкой в форматировании.

Нет.

skvitek ★★★
()
Ответ на: комментарий от ox55ff

Это должен быть сахар

В языке не должно быть синтаксического сахара.

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

Ну так отвечай на них.

Это не c++.

Это самый что ни на есть С++. Можешь заменить на string_view, если тебя смущает пересечение с С.

У любого способа форматирования в рантайме будут проблемы в этом случае.

Нет, у fmt все хорошо.

Нужно двигаться в сторону удобства, а не выдумывания комбайна, который может проглотить всё.

Нет, это С++. Зачем мне твоя интерполяция, если я не смогу ей нормально пользоваться на микроконтроллере или в модуле ядра?

Достаточно, чтобы сахар с

В языке не должно быть сахара. Если он добавляется – то он должен работать во всех возможных контекстах и покрывать все возможные использования.

Siborgium ★★★★★
()
Ответ на: комментарий от ox55ff

Правильно:
Говно:

Завтра придумают ещё что-то, что будет лучше предыдущих решений. Будем каждый раз орать - «Аааааа, говно!»?

Может вместо словесного обмазывания говном всего подряд, будем использовать слова «Хорошее решение» и «Решение ещё лучше».

pathfinder ★★★★
()
Ответ на: комментарий от ox55ff

Нужно двигаться в сторону удобства,

Раньше (я хз как сейчас) php пихали прям в html и была жуткая смесь логики и представления. Это называлось - говно-кодом.

mx__ ★★★★★
()
Ответ на: комментарий от ox55ff

Красиво! Именно так и надо подрывать пердаки закосневших в своем мышлении ретроградов!

Virtuos86 ★★★★★
()
Ответ на: комментарий от Siborgium

В языке не должно быть синтаксического сахара.

Range-based for loop уже выкорчевал из своего компилятора. А то двойные стандарты.

Ну так отвечай на них.

Я тебе уже ответил, что интерполяция превратится в обычный вызов функции.

Можешь заменить на string_view

Никаких дополнительных минусов по сравнению с классическими форматорами нет.

если я не смогу ей нормально пользоваться на микроконтроллере или в модуле ядра

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

В языке не должно быть сахара.

Повторяешься. Кстати, делал тут корутину. И о ужас co_return 1; на самом деле заменяется на co_await promise.return_value(1);. Ох уж этот отвратительный сахар. Выкорчевать и заставить писать руками.

ox55ff ★★★★★
()
Ответ на: комментарий от mx__

У тебя в любом случае смесь логики и представления. От того что ты переменные перечислишь после закрытия кавычек ничего не изменится.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

Range-based for loop уже выкорчевал из своего компилятора. А то двойные стандарты.

При чем здесь двойные стандарты и ranged-for?

Я тебе уже ответил, что интерполяция превратится в обычный вызов функции.

Вызов какой функции, с какими аргументами?

А почему ты ей не сможешь пользоваться.

Потому что я тебе обрисовал появляющиеся проблемы, от которых ты отмахиваешься «решаемо».

Ох уж этот отвратительный сахар. Выкорчевать и заставить писать руками.

Согласен. Достаточно было дать пользователю прямой доступ к объекту корутины.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

При чем здесь двойные стандарты и ranged-for?

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

Вызов какой функции, с какими аргументами?

Нужна поддержка интерполяции в ядре языка. Которая вычленит из единой строки текст, переменные и параметры форматирования. Далее это передаётся в реализацию функции форматирования из stdlib в том виде, в котором это будет удобно для реализации. На выходе получаем std::string.

Потому что я тебе обрисовал появляющиеся проблемы

Ты ничего не обрисовал. Давай пример где работает std::string s = fmt::format("The answer is {}.", value);, но не работает std::string s = $"The answer is {value}.".

прямой доступ к объекту корутины

Нет, писать co_return удобнее. Любители закатывать солнце вручную могут перейти на ассемблер. Там напишутся вдоволь.

ox55ff ★★★★★
()
Ответ на: комментарий от pathfinder

Может вместо словесного обмазывания говном всего подряд, будем использовать слова «Хорошее решение» и «Решение ещё лучше».

А вот это «Решение лучше лучшего», «Вообще агонь!», «Зачет, ништяк!»

BydymTydym
()
Ответ на: комментарий от ox55ff

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

Да, мог бы. И временами приходится. Но ты вырвал цитату из контекста – ranged-for можно использовать со всем, что отдаленно напоминает range (в широком смысле), и потому к нему нет таких претензий.

Нужна поддержка интерполяции в ядре языка.

Не нужна.

Далее это передаётся в реализацию функции форматирования из stdlib в том виде, в котором это будет удобно для реализации

Как мне добавить поддержку кастомных типов? Мне придется работать с реализацией, то есть как минимум какие-то типы и методы наружу отдавать придется.

На выходе получаем std::string.

Я не хочу std::string. Я хочу std::pmr::string. Я хочу std::wstring. Я хочу my::own::string. Я хочу const char*. Я хочу const unsigned char*. Я хочу std::string<char32_t>. Я хочу формировать строку в пре-аллоцированном буфере. Я хочу формировать строку лениво.

Ты ничего не обрисовал.

Не ври. Вот ссылка, сверху еще немного претензий.

Давай пример где работает

fmt::format умеет еще огромное количество вещей. В том числе практически все, что я обрисовал выше.

Зачем в ядро языка добавлять ущербанство, которое не может даже то, что может обычная библиотечная функция?

Нет, писать co_return удобнее.

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

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)

У fmt есть одна беда. Оно не локализуется от слова совсем. Если в форматной строке на уровне рантайма нельзя менять порядок следования подставляемых значений, то инструмент получается очень специфичный. В этом ключе я бы предпочёл что-то такое:

fmt::format(
  "String: {string}, integer: {int}, floating-point: {float}",
  {
    {"string", "this is string"},
    {"int", 42},
    {"float", 48000.0f}
  });

Но initializer-list в этом контексте выглядит, конечно, убого.

sadko4u ★★
()
Ответ на: комментарий от Siborgium

ущербанство

классное словечко, схоронил :)

yu-boot ★★★★
()
Ответ на: комментарий от ox55ff

Даже в D похожий DIP отклонили, а готовый PR так долго промариновали, что автор свалил на Zig.

dataman ★★★★
() автор топика
Ответ на: комментарий от ox55ff

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

firkax ★★★★★
()
Ответ на: комментарий от ox55ff

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

mazdai ★★★
()
Ответ на: комментарий от Anoxemian

Языки должны быть читаемыми. Какая формальная логика в убожестве “%s,%s,%s,%s,%s“ и дальше набором идентификаторов, по сравнению с их смузи записью на месте? Компилятор в любом случае сообщит о несовместимых типах, но в первом случае гораздо проще совершить логическую ошибку, перепутав параметры местами.

Logopeft ★★
()
Ответ на: комментарий от mazdai

В смысле? В рантайме printf(«ololo %s ololo», str) точно так же парсит длину str и выделяет буфер. Только ещё и читаемость хуже. А весь парсинг строк для интерполяции можно произвести в момент компиляции.

Logopeft ★★
()
Ответ на: комментарий от ox55ff

Внезапно переменная может быть массивом …

mx__ ★★★★★
()
Ответ на: комментарий от Siborgium

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

const char*

Сколько можно цеплятся за наследие старины глубокой?

Как добавить форматирование для кастомного типа?

Аналогично кастомному форматировани для стримов?

Что делать с локалями: пусть я в $«{a}{b}» хочу a форматировать в одной локали, а b в другой

Такие специфичные случаи можно и не поддерживать. Хочешь странного – пиши сам / использую 3p библиотеки.

trex6 ★★★★★
()
Ответ на: комментарий от Siborgium

потому к нему нет таких претензий

range for это сахар. Ты утверждал, что сахару в языке не место. А теперь выясняется, что ты трескаешь сгущёнку под одеялом.

Как мне добавить поддержку кастомных типов?

Это зависит от того как сделать поддержку интерполяции в ядре языка. Мне в голову пришёл следующий способ.

У нас есть пользовательские литералы.

OutputType operator "" _suffix(const char* str, size_t size);
// После чего можно писать
"abc"_suffix
// И будет вызван соответствующий оператор

Можно сделать похожий синтаксис для интерполяции:

std::string operator $"" _fmt(const char* str, Args&&... args)
{
    // Вызов классического форматера
    return fmt::format(str, std::forward<Args>(args)...);
}

// Использование
std::string str = $"a = {a}, b = {b}"_fmt;

// Что компилятором развернётся в вызов оператора
std::string str = $""_fmt("a = {}, b = {}", a, b);

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

Я не хочу std::string. Я хочу std::pmr::string. Я хочу std::wstring.

Пишешь соответствующий оператор с другим суффиксом, который возвращает нужный тебе тип. Для std::string будет реализация из коробки в stdlib.

Не ври. Вот ссылка, сверху еще немного претензий.

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

Зачем в ядро языка добавлять ущербанство

Затем, что все библиотеки для форматирования будут говном и ущербанством пока в ядре языка не появится интерполяция строк. И только с её появлением эти библиотеки смогут сделать удобную реализацию.

co_coco и co_keyword

А ты сначала придумай что ещё может понадобится кроме yield и return. Это стандартные операторы и очевидно они должны иметь удобный способ вызова.

ox55ff ★★★★★
()
Последнее исправление: ox55ff (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.