Доброго времени суток! В этой статье я хотел бы рассказать о существующих возможностях строкового форматирования в современном 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 << str << std::endl;
Здесь:
Строка-шаблон: 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 << str << std::endl;
Это довольно неплохой способ форматирования, несмотря на кажущуюся неуклюжесть:
- это самый быстрый способ строкового форматирования
- этот способ работает практически на всех версиях компиляторов, не требуя поддержки новых стандартов
Но, конечно, не обошлось и без недостатков:
- нужно знать заранее сколько памяти потребуется для результирующей строки, что не всегда возможно определить
- соответствие количества и типа аргументов и местозаполнителей не проверяется при передаче параметров извне (как в обертке над 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 << str << std::endl;
Класс std::stringstream
Класс std::stringstream — это основной способ строкового форматирования, который нам предоставляет C++:
std::stringstream ss;
ss << "I have " << apples << " apples and " << oranges << " oranges, so I have " << apples + oranges << " fruits";
std::string str = ss.str();
std::cout << str << std::endl;
Строго говоря, использование std::stringstream не является в полной мере строковым форматированием, так как вместо местозаполнителей мы вставляем в строку-шаблон аргументы. Это допустимо в простейших случаях, но в более сложных существенно ухудшает читаемость кода:
ss << "A[" << i1 << ", " << j1 << "] + A[" << i2 << ", " << j2 << "] = " << A[i1][j1] + A[i2][j2];
сравните с:
std::string str = format("A[%d, %d] + A[%d, %d] = %d", i1, j1, i2, j2, A[i1][j1] + A[i2][j2]);
Объект std::sringstream позволяет реализовать несколько интересных оберток, которые могут понадобится в дальнейшем.
Преобразование "чего угодно" в строку:
template<typename T> std::string to_string(const T &t)
{
std::stringstream ss;
ss << t;
return ss.str();
}
std::string str = to_string("5");
Преобразование строки во "что угодно":
template<typename T> 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)
{
return str;
}
int x = from_string<int>("5");
Преобразование строки во "что угодно" с проверкой:
template<typename T> 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)
{
ok = true;
return str;
}
bool ok = false;
int x = from_string<int>("x5", ok);
if (!ok) ...
Также, можно написать пару оберток для удобного использования std::stringstream в одну строку.
Использование объекта std::stringstream для каждого аргумента:
class fstr final : public std::string
{
public:
fstr(const std::string &str = "")
{
*this += str;
}
template<typename T> fstr &operator<<(const T &t)
{
*this += to_string(t);
return *this;
}
};
std::string str = fstr() << "I have " << apples << " apples and " << oranges << " oranges, so I have " << apples + oranges << " fruits";
Использование одного объекта std::stringstream для всей строки:
class sstr final
{
public:
sstr(const std::string &str = "")
: ss_(str)
{
}
template<typename T> sstr &operator<<(const T &t)
{
ss_ << t;
return *this;
}
operator std::string() const
{
return ss_.str();
}
private:
std::stringstream ss_;
};
std::string str = sstr() << "I have " << apples << " apples and " << oranges << " oranges, so I have " << apples + oranges << " fruits";
Забегая вперед, оказывается, что производительность 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<typename T> std::string to_string(const T &t)
{
std::stringstream ss;
ss << t;
return ss.str();
}
Библиотека 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 функцией, выделяя достаточно памяти и передавая произвольное количество параметров.
Для выделения памяти будем использовать следующую стратегию:
- сначала выделяем такое количество памяти, которого будет достаточно в большинстве случаев
- пробуем вызвать функцию форматирования
- если вызов закончился неудачей, выделим больше памяти и повторим предыдущий шаг
Для передачи параметров будем использовать механизм stdarg и функцию vsnprintf.
std::string format(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
std::vector<char> v(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<int>(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<size_t>(res) + 1;
v.clear();
v.resize(size);
}
}
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 << format("%s is a capital of %s", capital.c_str(), country.c_str()) << std::endl;
Шаблон с переменным количеством аргументов (Variadic Template)
В C++ начиная с C++11 появилась возможность использовать шаблоны с переменным количеством аргументов (Variadic Templates).
Такие шаблоны можно использовать при передаче аргументов в функцию форматирования. Также, нам больше не нужно заботиться о типах аргументов, так как мы можем использовать шаблонную to_string, которая была реализована ранее. Поэтому будем использовать порядковые местозаполнители.
Для получения всех аргументов отделяем первый аргумент, преобразуем его в строку, запоминаем и рекурсивно повторяем эту операцию. В случае отсутствия аргументов или при их окончании (конечная точка рекурсии) выполняем разбор строки-шаблона, подстановку аргументов и получаем результирующую строку.
Таким образом, у нас есть все, чтобы полностью реализовать функцию форматирования: парсинг строки-шаблона, сбор и преобразование в строку всех параметров, подстановку параметров в строку-шаблон и получение результирующей строки:
std::string vtformat_impl(const std::string &fmt, const std::vector<std::string> &strs)
{
static const char FORMAT_SYMBOL = '%';
std::string res;
std::string buf;
bool arg = false;
for (int i = 0; i <= static_cast<int>(fmt.size()); ++i)
{
bool last = i == static_cast<int>(fmt.size());
char ch = fmt[i];
if (arg)
{
if (ch >= '0' && ch <= '9')
{
buf += ch;
}
else
{
int num = 0;
if (!buf.empty() && buf.length() < 10)
num = atoi(buf.c_str());
if (num >= 1 && num <= static_cast<int>(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<typename Arg, typename ... Args> std::string vtformat_impl(const std::string &fmt, std::vector<std::string> &strs, Arg arg, Args ... args)
{
strs.push_back(to_string(arg));
return vtformat_impl(fmt, strs, args ...);
}
std::string vtformat(const std::string &fmt)
{
return fmt;
}
template<typename Arg, typename ... Args> std::string vtformat(const std::string &fmt, Arg arg, Args ... args)
{
std::vector<std::string> strs;
return vtformat_impl(fmt, strs, arg, args ...);
}
Алгоритм получился достаточно эффективным, работает за один проход по строке форматирования. В случае, если вместо местозаполнителя не удается вставить аргумент, он остается без изменений, исключений не генерируется.
Примеры использования:
std::cout << vtformat("I have %1 apples and %2 oranges, so I have %3 fruits", apples, oranges, apples + oranges) << std::endl;
I have 5 apples and 7 oranges, so I have 12 fruits
std::cout << vtformat("%1 + %2 = %3", 2, 3, 2 + 3) << std::endl;
2 + 3 = 5
std::cout << vtformat("%3 = %2 + %1", 2, 3, 2 + 3) << std::endl;
5 = 3 + 2
std::cout << vtformat("%2 = %1 + %1 + %1", 2, 2 + 2 + 2) << std::endl;
6 = 2 + 2 + 2
std::cout << vtformat("%0 %1 %2 %3 %4 %5", 1, 2, 3, 4) << std::endl;
%0 1 2 3 4 %5
std::cout << vtformat("%1 + 1% = %2", 54, 54 * 1.01) << std::endl;
54 + 1% = 54.540000
std::string country = "Russia";
const char *capital = "Moscow";
std::cout << vtformat("%1 is a capital of %2", capital, country) << std::endl;
Moscow is a capital of Russia
template<typename T> std::ostream &operator<<(std::ostream &os, const std::vector<T> &v)
{
os << "[";
bool first = true;
for (const auto &x : v)
{
if (first)
first = false;
else
os << ", ";
os << x;
}
os << "]";
return os;
}
std::vector<int> v = {1, 4, 5, 2, 7, 9};
std::cout << vtformat("v = %1", v) << std::endl;
v = [1, 4, 5, 2, 7, 9]
Сравнение производительности
Сравнение производительности to_string и std::to_string, миллисекунд на миллион вызовов
int, мс | long long, мс | double, мс | |
---|---|---|---|
to_string | 681 | 704 | 1109 |
std::to_string | 130 | 201 | 291 |
Сравнение производительности функций форматирования, миллисекунд на миллион вызовов
мс | |
---|---|
fstr | 1308 |
sstr | 1243 |
format | 788 |
boost::format | 2554 |
vtformat | 2022 |
Спасибо за внимание. Замечания и дополнения приветствуются.
Комментарии (54)
cranium256
07.01.2017 08:21+2В наследии C самая крутая фишка не в том, что значения аргументов вставляются в указанные места строки формата (хотя это тоже, как вы правильно отметили, немаловажно). А в том, что эти значения форматируются в соответствии с указанными спецификациями.
На сколько я вижу, подобной функциональностью обладает только вариант «Обертка над vsnprintf». Но в нём нет контроля соответствия спецификации формата и типа аргумента.maaGames
07.01.2017 11:08Насколько помню, boost поддерживает спецификацию типа. По крайней мере, когда я писал аналог буста, то мне удалось сделать и спецификаторы типа и порядковые номера одновременно. Делал ради фана, ни в одном проекте так и не использовал библиотеку. Про производительность тоже ничего сказать не могу, скорее всего не сильно быстро.
yiselieren
07.01.2017 17:11+1По поводу контроля над спецификациями формата и типа аргумента:
Добавляем __attribute__ ((format (printf, N, M)))
после прототипа функции.
demp
07.01.2017 12:37+9Зачем использовать boost.format, если достаточно давно есть fmt, ранее известная как cppformat.
И у любителей compile-time недавно появилось поле для экспериментов: pprintpp
При написании своего велосипеда полезно понимать, чем он лучше остальных. Для сравнения производительности с другими можно сравнить результаты из format-benchmark.
mikeus
07.01.2017 17:46И у любителей compile-time недавно появилось поле для экспериментов: pprintpp
А это прикольно. Но взглянув на этот фрагмент, можно понять что, если в примере использования:
заменить хотя бы на:#include <pprintpp.hpp> #include <cstdio> int main() { pprintf("{} hello {s}! {}\n", 1, "world", 2.0) ; }
то все посыпется…#include <pprintpp.hpp> #include <cstdio> int main() { const char *f = "{} hello {s}! {}\n"; pprintf(f, 1, "world", 2.0) ; }
ilynxy
07.01.2017 12:56Зачем использовать boost.format, если достаточно давно есть fmt, ранее известная как cppformat.
Как у fmt сейчас с локалями? Поддерживает? Я не слежу, но совсем недавно была печаль.demp
07.01.2017 20:53Я тоже не слежу, кажется толком не поддерживает. Но вроде бы форматирование чисел с группировкой по разрядам использует глобальную локаль для получения разделителя и количества цифр в группах.
Вообще, локали одно из самых тормозных мест в стандартной библиотеке, и в плане компиляции, и времени выполнения. Так что автора cppformat можно понять, что он не хочет с ними связываться.
monostate
08.01.2017 22:48Локали поддерживаются как в питоне через спецификатор форматирования
n
(http://fmtlib.net/3.0.0/syntax.html#format-specification-mini-language).
Форматирование по умолчанию не зависит от локали по причинам указанным в http://fmtlib.net/Text%20Formatting.html#Locale
forester11
07.01.2017 13:16-2А чем плох snprintf?
Быстрее и компактней ничего наверно нет, компиляторы выдают предупреждения на несоответствие типов. Нельзя определить точно размер буфера? Да и ненадо, на стеке выделить 256/512/..., а организацию вызовов (интерфейсы) можно организовать так что свободно летающие std::string не требуются. Причем на стеке память выделяется за константное (бесплатное) время а не поиск в хипе свободного блока по хитрому алгоритму с синхронизацией тредов.
Т.е. можно вообще не тратить время на изобретение новых классов, использовать vsnprintf, и выпилить в дизайне необходимость в свободно летающих std::string (часто форматирование строк сугубо задача UI модуля, вот в нем и можно скрыть эти заморочки прокинув стековые строки прямо в native API UI библиотеки). Т.е. тот же printf печатает прямо в стандартный вывод без копирования строк, а некоторые библиотеки дают возможность делать sprintf прямо в UI контрол. Тогда зачем нам временный std::string? Так, построить код ради кода.snizovtsev
07.01.2017 14:25+3Лишь первое, что пришло в голову:
- Нельзя использовать нестандартные типы. Особенно раздражает с std::string и string_view;
- Если мне не изменяет память — сишный formatstring непортабелен (%d, %ld, %lld). Отсюда вырвиглазные макросы аля PRIi64;
- Назначение — далеко не только UI. Еще логирование и отладка как минимум.
staticlab
07.01.2017 17:40Ещё в C++ из коробки поддерживается форматирование чисел в соответствии с локалью.
win32asm
07.01.2017 13:21+1Александреску давно возмущался несовершенством мира вообще и форматирования строк в С++ в частности — https://erdani.com/publications/cuj-2005-08.pdf (ссылки на код в статье битые 8-( )
Учитывая что воз и ныне там, кажется, на эту фичу слегка подзабили. Правда я в С++14/17 не вчитывался на эту тему…
Rend
07.01.2017 13:35+5В случае обёртки над vsnprintf вместо многочисленных попыток выделить память можно просто спросить у функции, сколько же ей нужно.
string string_format(const char* format, ...) { va_list args; va_start(args, format); int buf_len = vsnprintf(nullptr, 0, format, args); unique_ptr<char[]> strBuf(new char[buf_len + 1]); vsnprintf(strBuf.get(), buf_len + 1, format, args); return string(strBuf.get()); }
Effolkronium
07.01.2017 17:12+1Лучше так (14 стандарт):
auto strBuf = std::make_unique<char[]>(buf_len + 1);
5nw
07.01.2017 17:31+1К сожалению, она не всегда знает, сколько ей нужно. На некоторых платформах всегда возвращается -1.
Rend
07.01.2017 18:56Могу лишь предположить, что это старые версии ОС с поддержкой C89/C90, где vsnprintf стандартом вообще не определена, или нестандартные библиотеки. С99 и POSIX (понимаю, с 1003.1-2005) требуют возвращать значение. К сожалению, не нашёл требований к данной функции по стандарту C++; скорее всего, тоже требует.
Но на возврат -1 действительно, лучше проверять.
kibb
07.01.2017 23:27+1Можно пользоваться asprintf()/vasprintf(), которые есть в любой современной libc, и не заботиться о выделении памяти.
Altren
07.01.2017 17:12+4Рекомендую ознакомится с библиотекой fmt: https://github.com/fmtlib/fmt
По сути она решает все указанные проблемы, имея производительность близкую к printf.
015z
07.01.2017 17:12+1Наследие C
Строковое форматирование в C осуществляется с помощью семейства функций Xprintf.
…
Но, конечно, не обошлось и без недостатков:
нужно знать заранее сколько памяти потребуется для результирующей строки, что не всегда возможно определить
Можно вызвать snprintf без указания выходного буфера:
int res = snprintf(NULL, 0, "I have %d apples and %d oranges, so I have %d fruits", apples, oranges, apples + oranges);
Тогда res будет содержать необходимое количество байт (без нуль-байта). https://linux.die.net/man/3/snprintf:
Conforming To The fprintf(), printf(), sprintf(), vprintf(), vfprintf(), and vsprintf() functions conform to C89 and C99. The snprintf() and vsnprintf() functions conform to C99. Concerning the return value of snprintf(), SUSv2 and C99 contradict each other: when snprintf() is called with size=0 then SUSv2 stipulates an unspecified return value less than 1, while C99 allows str to be NULL in this case, and gives the return value (as always) as the number of characters that would have been written in case the output string has been large enough.
Да, это два прохода, но знать заранее ничего не нужно, а нужно следить за тем, что остальные аргументы идентичны.
соответствие количества и типа аргументов и местозаполнителей не проверяется при передаче параметров извне (как в обертке над vsnprintf, реализованной ниже), что может привести к ошибкам при выполнении программы
Комплятор gcc точно выдает предупреждение, если выставлена опция -Wformat (включена в -Wall). https://linux.die.net/man/1/gcc:
-Wformat Check calls to "printf" and "scanf", etc., to make sure that the arguments supplied have types appropriate to the format string specified, and that the conversions specified in the format string make sense. This includes standard functions, and others specified by format attributes, in the "printf", "scanf", "strftime"...
YouCompleteMe, работающий через libclang, также подсвечивает такие места. Но там есть пара оговорок, посмотрите, пожалуйста.5nw
07.01.2017 17:35Да, это два прохода, но знать заранее ничего не нужно, а нужно следить за тем, что остальные аргументы идентичны.
На некоторых платформах Xprintf() всегда возвращают -1, поэтому к сожалению, двумя вызовами в общем случае не обойтись
Комплятор gcc точно выдает предупреждение, если выставлена опция -Wformat (включена в -Wall).
Да, но только при непосредственном вызове. При передаче параметров извне предупреждения не будет:
printf("%d %d %d\n", 1, 2); // здесь есть warning std::cout << format("%d %d %d", 1, 2) << std::endl; // а здесь уже нет
015z
07.01.2017 19:02На некоторых платформах Xprintf() всегда возвращают -1, поэтому к сожалению, двумя вызовами в общем случае не обойтись
Ткните носом, пожалуйста, на каких именно.5nw
07.01.2017 19:05Сейчас точно не помню, кажется на SPARCе со специфическим линуксом
Там же, насколько я помню, vsnprintf портил arglist
Деталей по некоторым причинам полностью раскрыть не могу, но платформа очень экзотическая
Но думаю она не единственная, где такое поведение015z
07.01.2017 19:22Я думаю, специфические линуксы и архитектуры потребуют особого внимания и без snprintf, но я понял, спасибо.
Vooon
07.01.2017 17:16+1Я пользуюсь такой оберткой:
template<typename ... Args> std::string format(const std::string &fmt, Args ... args) { // C++11 specify that string store elements continously std::string ret; auto sz = std::snprintf(nullptr, 0, fmt.c_str(), args...); ret.reserve(sz + 1); ret.resize(sz); // to be sure there have room for \0 std::snprintf(&ret.front(), ret.capacity() + 1, fmt.c_str(), args...); return ret; }
Она конечно хуже буста (нет проверки типов, хотя это делает компилятор), и не умеет работать со строками, но все же удобна.
5nw
07.01.2017 17:25Если std::snprintf из C++11 работает одинаково на всех платформах, то это неплохой вариант, спасибо.
Vooon
07.01.2017 17:35Я не знаю, этот код работает только на линуксе, и вроде кто-то собирал под макось (но я не знаю результат).
http://ru.cppreference.com/w/cpp/io/c/fprintf — но на платформе, где printf() не по стандарту нет надежды, что stdc++ будет соответствовать стандарту.
Еще нужно бы добавить assert на sz >= 0, но если sz = -1, то все равно должно будет упасть на resize().
Alesh
07.01.2017 17:59+1Да с форматированием в С++ почему то до сих пор, как в статусе из соц.сетей — "все сложно") Даже странно, не ужели никому реально не надо.
ZaMaZaN4iK
07.01.2017 19:27стандарт меняется довольно медленно. Сторонние библиотеки всегда можно использовать. Благо, что много годных либ header-only.
Хорошо, что в последнее время за стандарт взялись. Но всё равно, медленно всё проходит…
mikeus
07.01.2017 18:42Более подробно о поведении функций Xprintf на различных платформах можно почитать здесь.
Упомянутое различие для случая с Windows задокументировано в MSDN:
The snprintf function always stores a terminating null character, truncating the output if necessary. The _snprintf family of functions only appends a terminating null character if the formatted string length is strictly less than count characters.
Честно говоря я никогда не понимал и не разбирался зачем майкрософт ввел ещё серию функций _snprintf_s. Что в них более безопасного?
Sazonov
08.01.2017 02:48У вас незначительная логическая ошибка в функции from_string. Для неё стоит сделать явную специализацию для типа std::string. Код, который не будет работать:
auto x = from_string("1 2"); // x == "1";5nw
08.01.2017 02:52Я использую ее так:
auto x = from_string<int>("1 2");
так должно работатьSazonov
08.01.2017 20:54+1Если «так должно работать», то зачем вам шаблоны? Пишите явную имплементацию для типа int. Если же делаете шаблонную функцию, то делайте так, чтобы она работала с любыми типами.
Проверка (раскомментируйте код, чтобы заработало как надо)
#include <iostream> #include <string> #include <sstream> template<typename T> 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) { return str; } */ int main() { auto x = from_string<std::string>("1 2"); std::cout << x; // Вывод "1" вместо "1 2" return 0; }
5nw
08.01.2017 21:09Если «так должно работать», то зачем вам шаблоны?
Как раз чтобы не писать миллион имплементаций для всех возможных типов
Понял, о чем Вы
Да, это специфика operator<< для строк
Исправлю, спасибо
Renat060888
08.01.2017 17:52В целом интересно. Но я чето не понял как С++11-шный вариант автора работает с std стрингами. Там же вылезет «error: no matching function for call to 'to_string(std::basic_string&)'»
5nw
08.01.2017 17:55Странно, но у меня все работает:
std::string str = "hello"; std::cout << to_string(str) << std::endl; hello
Вероятно у Вас в коде using namespace std; и вместо шаблонного to_string вызывается std::to_string
newnon
08.01.2017 19:20я уже давно использую эту библиотечку https://github.com/fmtlib/fmt
и по скорости и по надежности прям то что надо и c#/python стиль и старый добрый C стиль
Shamov
09.01.2017 12:28Любопытно, что при разработке подобных штук люди всегда ограничиваются форматированием самых простейших типов и никогда не пытаются расширить этот тривиальный набор. Например, добавить плейсхолдер для форматирования IP-адреса, для первого (или второго) элемента std::pair, для автоматической подстановки текста ошибки по её коду. Или сделать крутой навороченный плейсхолдер для удобного вывода массивов. Точнее, для общего случая любых контейнеров/ranges. Или же можно было бы вообще выйти за флажки и добавить плейсхолдер для подстановки слова в указанном падеже и числе. Ну, типа, вот так:
format("счёт пополнен %d %s", 2, ablative("рубль")); // => "счёт пополнен 2 рублями"
dendron
11.01.2017 00:52+1Со строками в C++ традиционно всё плохо, к сожалению. Новые костыли оказываются не лучше предыдущих, стандарт распухает, и так снова и снова.
Satus
Было бы интереснее взглянуть на реализацию форматирования строк на шаблонах и constexpr, которая будет сама переключаться между compile-time и runtime форматированием когда нужно.
TargetSan
Насколько я знаю, нет возможности превратить строковой литерал в параметр типа чтобы распарсить и проверить его в компайл-тайме. Точнее, вроде как можно — но на практике оказывается что нельзя.
DistortNeo
Ничего, скоро constexpr разовьются до такой степени, что можно будет и такой код писать. Впрочем, я в этом смысла не вижу, т.к. парсинг в рантайме не должен хоть сколько-нибудь значимо замедлить форматирование строки, а вот геморроя с разбором параметров форматирования это добавит немало.
Пробовал, кстати, сам написать велосипед — чисто для практики с variadic templates, только форматирование хотел сделать в стиле C#:
my::string::Format("Hello, {0}! You are {1:0.00}", "Vasja", 12.0f);
Если вкратце:
Args...
преобразовывался в std::tuple<StringWriter<Args...>>, по которому затем строился массив(StringWriterBase*)[N]
, к которому и происходило обращение по индексу аргумента в стилеarr[index]->Write(target_stringbuilder, format_string_slice);
Велосипед, как ни странно, работал, но вот реализовывать поддержку всех опций форматирования строк как в .NET не было ни малейшего желания.
Antervis
там немного в другом проблема. constexpr не умеет работать с кучей, а сделать constexpr string конструктор через small string optimization невозможно т.к. в языке нет возможности перегружать функции по длине и/или статичности строкового литерала