Std string format cpp

Formatting library (since C++20)

The text formatting library offers a safe and extensible alternative to the printf family of functions. It is intended to complement the existing C++ I/O streams library.

Contents

[edit] Formatting functions

[edit] Extensibility support and implementation detail

[edit] Notes

Feature-test macro Value Std Comment
__cpp_lib_format 201907L (C++20) Text formatting
202106L (C++20)
(DR)
Compile-time format string checks;
Reducing parameterization of std::vformat_to
202110L (C++20)
(DR)
Fixing locale handling in chrono formatters;
Supporting non-const-formattable types
202207L (C++23) Exposing std::basic_format_string ;
Clarify handling of encodings in localized formatting of chrono types
__cpp_lib_format_ranges 202207L (C++23) Formatting ranges

We intentionally treat the addition of std::basic_format_string (P2508) as a defect report because all known implementations make these components available in C++20 mode, although it is not so categorized officially.

[edit] Example

#include #include int main() { std::string message = std::format("The answer is <>.", 42); assert(message == "The answer is 42."); }

[edit] Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
P2418R2 C++20 objects that are neither const-formattable nor copyable
(such as generator-like objects) are not formattable
allow formatting these objects
(relaxed formatter requirements)
P2508R1 C++20 there’s no user-visible name for this facility the name basic_format_string is exposed

Источник

О строковом форматировании в современном C++

Доброго времени суток! В этой статье я хотел бы рассказать о существующих возможностях строкового форматирования в современном C++, показать свои наработки, которые я уже несколько лет использую в реальных проектах, а также сравнить производительность различных подходов к строковому форматированию.

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

Для наглядности небольшой пример:

int apples = 5; int oranges = 7; std::string str = format("I have %d apples and %d oranges, so I have %d fruits", apples, oranges, apples + oranges); std::cout 

Здесь:
Строка-шаблон: I have %d apples and %d oranges, so I have %d fruits
Местозаполнители: %d, %d, %d
Аргументы: apples, oranges, apples + oranges

При выполнении примера, получаем результирующую строку

I have 5 apples and 7 oranges, so I have 12 fruits

Теперь посмотрим, что же нам предоставляет C++ для строкового форматирования.

Наследие C

Строковое форматирование в C осуществляется с помощью семейства функций Xprintf. С тем же успехом, мы можем воспользоваться этими функциями и в C++:

char buf[100]; int res = snprintf(buf, sizeof(buf), "I have %d apples and %d oranges, so I have %d fruits", apples, oranges, apples + oranges); std::string str = "error!"; if (res >= 0 && res < sizeof(buf)) str = buf; std::cout 

Это довольно неплохой способ форматирования, несмотря на кажущуюся неуклюжесть:

  • это самый быстрый способ строкового форматирования
  • этот способ работает практически на всех версиях компиляторов, не требуя поддержки новых стандартов

Но, конечно, не обошлось и без недостатков:

  • нужно знать заранее сколько памяти потребуется для результирующей строки, что не всегда возможно определить
  • соответствие количества и типа аргументов и местозаполнителей не проверяется при передаче параметров извне (как в обертке над vsnprintf, реализованной ниже), что может привести к ошибкам при выполнении программы

Функция std::to_string()

Начиная с C++11 в стандартной библиотеке появилась функция std::to_string(), которая позволяет преобразовать передаваемое значение в строку. Функция работает не со всеми типами аргументов, а только со следующими:

  • int
  • long
  • long long
  • unsinged int
  • unsinged long
  • unsigned long long
  • float
  • double
  • long double
std::string str = "I have " + std::to_string(apples) + " apples and " + std::to_string(oranges) + " oranges, so I have " + std::to_string(apples + oranges) + " fruits"; std::cout 

Класс std::stringstream

Класс std::stringstream — это основной способ строкового форматирования, который нам предоставляет C++:

Строго говоря, использование std::stringstream не является в полной мере строковым форматированием, так как вместо местозаполнителей мы вставляем в строку-шаблон аргументы. Это допустимо в простейших случаях, но в более сложных существенно ухудшает читаемость кода:

ss std::string str = format("A[%d, %d] + A[%d, %d] = %d", i1, j1, i2, j2, A[i1][j1] + A[i2][j2]);

Объект std::sringstream позволяет реализовать несколько интересных оберток, которые могут понадобится в дальнейшем.

Преобразование "чего угодно" в строку:

template std::string to_string(const T &t)
std::string str = to_string("5");

Преобразование строки во "что угодно":

template T from_string(const std::string &str) < std::stringstream ss(str); T t; ss >> t; return t; > template<> std::string from_string(const std::string &str)

Преобразование строки во "что угодно" с проверкой:

template T from_string(const std::string &str, bool &ok) < std::stringstream ss(str); T t; ss >> t; ok = !ss.fail(); return t; > template<> std::string from_string(const std::string &str, bool &ok)
bool ok = false; int x = from_string("x5", ok); if (!ok) . 

Также, можно написать пару оберток для удобного использования std::stringstream в одну строку.

Использование объекта std::stringstream для каждого аргумента:

class fstr final : public std::string < public: fstr(const std::string &str = "") < *this += str; >template fstr &operator <<(const T &t) < *this += to_string(t); return *this; >>;

Использование одного объекта std::stringstream для всей строки:

class sstr final < public: sstr(const std::string &str = "") : ss_(str) < >template sstr &operator <<(const T &t) < ss_ << t; return *this; >operator std::string() const < return ss_.str(); >private: std::stringstream ss_; >;

Забегая вперед, оказывается, что производительность std::to_string в 3-4 раза выше, чем у to_string, реализованной с помощью std::stringstream. Поэтому, логично будет использовать std::to_string для подходящих типов, а для всех остальных использовать шаблонную to_string:

std::string to_string(int x) < return std::to_string(x); >std::string to_string(unsigned int x) < return std::to_string(x); >std::string to_string(long x) < return std::to_string(x); >std::string to_string(unsigned long x) < return std::to_string(x); >std::string to_string(long long x) < return std::to_string(x); >std::string to_string(unsigned long long x) < return std::to_string(x); >std::string to_string(float x) < return std::to_string(x); >std::string to_string(double x) < return std::to_string(x); >std::string to_string(long double x) < return std::to_string(x); >std::string to_string(const char *x) < return std::string(x); >std::string to_string(const std::string &x) < return x; >template std::string to_string(const T &t)

Библиотека boost::format

Набор библиотек boost является мощным средством, отлично дополняющим средства языка C++ и стандартной библиотеки. Строковое форматирование представлено библиотекой boost::format.

Поддерживается указание как типовых местозаполнителей:

std::string str = (boost::format("I have %d apples and %d oranges, so I have %d fruits") % apples % oranges % (apples + oranges)).str();
std::string str = (boost::format("I have %1% apples and %2% oranges, so I have %3% fruits") % apples % oranges % (apples + oranges)).str();

Единственный недостаток boost::format — низкая производительность, это самый медленный способ строкового форматирования. Также этот способ неприменим, если в проекте нельзя использовать сторонние библиотеки.

Итак, получается, что C++ и стандартная библиотека не предоставляют нам удобных средств строкового форматирования, поэтому будем писать что-то свое.

Обертка над vsnprintf

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

Для выделения памяти будем использовать следующую стратегию:

  1. сначала выделяем такое количество памяти, которого будет достаточно в большинстве случаев
  2. пробуем вызвать функцию форматирования
  3. если вызов закончился неудачей, выделим больше памяти и повторим предыдущий шаг

Для передачи параметров будем использовать механизм stdarg и функцию vsnprintf.

std::string format(const char *fmt, . ) < va_list args; va_start(args, fmt); std::vectorv(1024); while (true) < va_list args2; va_copy(args2, args); int res = vsnprintf(v.data(), v.size(), fmt, args2); if ((res >= 0) && (res < static_cast(v.size()))) < va_end(args); va_end(args2); return std::string(v.data()); >size_t size; if (res < 0) size = v.size() * 2; else size = static_cast(res) + 1; v.clear(); v.resize(size); va_end(args2); > >
std::string str = format("I have %d apples and %d oranges, so I have %d fruits", apples, oranges, apples + oranges);

Здесь стоит разъяснить пару нюансов. Возвращаемое значение функций Xprintf зависит от платформы, на некоторых платформах, в случае неуспеха, возвращается -1, в этом случае мы увеличиваем буфер в два раза. На других платформах возвращается длина результирующей строки (без учета нулевого символа), в этом случае мы сразу можем выделить столько памяти, сколько необходимо. Более подробно о поведении функций Xprintf на различных платформах можно почитать здесь. Также, на некоторых платформах, vsnprintf() "портит" список аргументов, поэтому копируем его перед вызовом.

Я начал использовать эту функцию еще до появления C++11 и с небольшими изменениями продолжаю использовать по сегодняшний день. Основное неудобство при использовании — отсутствие поддержки std::string в качестве аргументов, поэтому нужно не забывать добавлять .c_str() ко всем строковым аргументам:

std::string country = "Great Britain"; std::string capital = "London"; std::cout 

Шаблон с переменным количеством аргументов (Variadic Template)

В C++ начиная с C++11 появилась возможность использовать шаблоны с переменным количеством аргументов (Variadic Templates).

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

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

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

std::string vtformat_impl(const std::string &fmt, const std::vector &strs) < static const char FORMAT_SYMBOL = '%'; std::string res; std::string buf; bool arg = false; for (int i = 0; i (fmt.size()); ++i) < bool last = i == static_cast(fmt.size()); char ch = fmt[i]; if (arg) < if (ch >= '0' && ch else < int num = 0; if (!buf.empty() && buf.length() < 10) num = atoi(buf.c_str()); if (num >= 1 && num (strs.size())) res += strs[num - 1]; else res += FORMAT_SYMBOL + buf; buf.clear(); if (ch != FORMAT_SYMBOL) < if (!last) res += ch; arg = false; >> > else < if (ch == FORMAT_SYMBOL) < arg = true; >else < if (!last) res += ch; >> > return res; > template inline std::string vtformat_impl(const std::string& fmt, std::vector& strs, Arg&& arg, Args&& . args) < strs.push_back(to_string(std::forward(arg))); return vtformat_impl(fmt, strs, std::forward(args) . ); > inline std::string vtformat(const std::string& fmt) < return fmt; >template inline std::string vtformat(const std::string& fmt, Arg&& arg, Args&& . args) < std::vectorstrs; return vtformat_impl(fmt, strs, std::forward(arg), std::forward(args) . ); >

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

std::cout std::ostream &operator <<(std::ostream &os, const std::vector&v) < os os std::vector v = ; std::cout 

Сравнение производительности

Сравнение производительности to_string и std::to_string, миллисекунд на миллион вызовов

int, мс long long, мс double, мс
to_string 681 704 1109
std::to_string 130 201 291

image

Сравнение производительности функций форматирования, миллисекунд на миллион вызовов

мс
fstr 1308
sstr 1243
format 788
boost::format 2554
vtformat 2022

image

Спасибо за внимание. Замечания и дополнения приветствуются.

Источник

Читайте также:  Python reading text files line by line
Оцените статью